diff --git a/DEPS b/DEPS
index 54bcb0b..6ee50a37 100644
--- a/DEPS
+++ b/DEPS
@@ -299,7 +299,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': 'ef1a2d488c86cc10f9d96b94f797f42fb4273095',
+  'src_internal_revision': 'c3f48770647d6163d3d4082206808ec6fea359ae',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
@@ -307,11 +307,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'fffec1a2cbac6191832d0ad4e27eb35685a2669f',
+  'v8_revision': '299a02b2c82bfd6bffe69b1675d154190290bbcd',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'ef61ff8db5433d5d303df9ba6691036fcbf8bd91',
+  'angle_revision': '34d30914048ac98a6f50f84aa630cc285b75bc79',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -395,7 +395,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': '11882909f79cbad52255b8cadac40a7e115f518c',
+  'devtools_frontend_revision': '4c5c7fd1931eaa285aa1ee7484a158529e32138a',
   # 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.
@@ -419,7 +419,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '5c35c84f1b68cd0614266d1ba605d23bd3165ea8',
+  'dawn_revision': '5e815131e2179fa7ab3ceca4e0ad8f09ad04ca37',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -527,11 +527,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling llvm-libc
   # and whatever else without interference from each other.
-  'compiler_rt_revision': 'c9899c3706743f47be2397731ed25e10afb32321',
+  'compiler_rt_revision': 'a679a8b8e33c2ba291fcab33ab2c628aa72c7aea',
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
-  'libcxx_revision':       'bb79a34585926c20974cd7191b730ac94133a992',
+  'libcxx_revision':       '917609c669e43edc850eeb192a342434a54e1dfd',
 
   # GN CIPD package version.
   'gn_version': 'git_revision:90478db6b59b9bebf7ca4cf912d860cf868e724c',
@@ -1152,7 +1152,7 @@
   },
 
   'src/chrome/release_scripts': {
-      'url': Var('chrome_git') + '/chrome/tools/release/scripts' + '@' + 'e4354acf6ce7f32ee869ddb5dd2cabd6ca2f8b2f',
+      'url': Var('chrome_git') + '/chrome/tools/release/scripts' + '@' + '6e0c25fb2cf1d16a46c03138d9745536a185e93e',
       'condition': 'checkout_chrome_release_scripts',
   },
 
@@ -1495,7 +1495,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'ab79cee2b3c5a6548430c71adddeba5e02a6ddc4',
+    '86860c4de44765ed2d195926b9d3dd6ed1d20efd',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1524,7 +1524,7 @@
   },
 
   'src/ios/third_party/material_components_ios/src': {
-      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '7c19c196a74207c263cedffe6eefe14761e69cf8',
+      'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + '91a227c283a463f49d0b55474f9686b67aa2d9da',
       'condition': 'checkout_ios',
   },
 
@@ -1654,7 +1654,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'akKYKFZbO23owp7RUV84Jb2KzTHJ4ntMyN8HuJJIRZoC',
+          'version': 'JF5sZnHpxvqTCPlwlSSvr296yGpWharAZaJzJ6I9EGwC',
       },
     ],
     'condition': 'checkout_android and non_git_source',
@@ -2535,7 +2535,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '6540d2901b623f1cdca4772e5fe6ef873f558b42',
+    Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '6acaf607621ce5a117dc1a9773f27aa0d4c77b5b',
 
   'src/base/tracing/test/data': {
     'bucket': 'perfetto',
@@ -3025,7 +3025,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/boca_app/app',
-        'version': 'yj-paVA1NAoU1ClZEVtbJexXYw__vAM6HcjIN9g700gC',
+        'version': 'jU4QG3Inor_UzXvRcJLZ4fjvMTl4GrXu1j1eYqEfxiEC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3080,7 +3080,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'd5LvDuBswPNNUim3u__LYXLXxB6LAq5WkiV7I1nUXF4C',
+        'version': 'aY-B1X9zSNFzhjV3DIA07MkLgfKLf1lnHOg4fa6lbVsC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4689,7 +4689,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '2aad56f4f3ad2277277e35cfa1ab3ec55937bf81',
+        '5faebbb3e45a687dae0a0cd09f8f8c69e9897386',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/android_webview/tools/run_cts.py b/android_webview/tools/run_cts.py
index 0c05ae6..ccb105d 100755
--- a/android_webview/tools/run_cts.py
+++ b/android_webview/tools/run_cts.py
@@ -542,14 +542,14 @@
   parser.add_argument('-m',
                       '--module-apk',
                       dest='module_apk',
-                      help='CTS module apk name in the --cts-gcs-path'
-                      ' file, without the path prefix.')
+                      help=('CTS module apk name in the --cts-gcs-path '
+                            'file, without the path prefix.'))
   parser.add_argument(
       '--avd-config',
       type=os.path.realpath,
-      help='Path to the avd config textpb. '
-           '(See //tools/android/avd/proto for message definition'
-           ' and existing textpb files.)')
+      help=('Path to the avd config textpb. '
+            '(See //tools/android/avd/proto for message definition'
+            ' and existing textpb files.)'))
   # Emulator log will be routed to stdout when "--emulator-debug-tags" is set
   # without an output_manager.
   # Mark this arg as unused for run_cts to avoid dumping too much swarming log.
@@ -567,18 +567,18 @@
   parser.add_argument('--cts-archive-dir',
                       type=os.path.realpath,
                       default=_DEFAULT_CTS_ARCHIVE_DIR,
-                      help='Path to where CTS archives are stored. '
-                      'Defaults to: ' + _DEFAULT_CTS_ARCHIVE_DIR)
+                      help=('Path to where CTS archives are stored. '
+                            'Defaults to: ' + _DEFAULT_CTS_ARCHIVE_DIR))
   parser.add_argument('--tradefed-aapt-path',
                       type=os.path.realpath,
                       default=_DEFAULT_TRADEFED_AAPT_PATH,
-                      help='Path to where AAPT binary is located. '
-                      'Defaults to: ' + _DEFAULT_TRADEFED_AAPT_PATH)
+                      help=('Path to where AAPT binary is located. '
+                            'Defaults to: ' + _DEFAULT_TRADEFED_AAPT_PATH))
   parser.add_argument('--tradefed-adb-path',
                       type=os.path.realpath,
                       default=_DEFAULT_TRADEFED_ADB_PATH,
-                      help='Path to where ADB binary is located. '
-                      'Defaults to: ' + _DEFAULT_TRADEFED_ADB_PATH)
+                      help=('Path to where ADB binary is located. '
+                            'Defaults to: ' + _DEFAULT_TRADEFED_ADB_PATH))
 
   # The variations test seed file should be in JSON format. Please look
   # in //components/variations/test_data/cipd for examples of variations
@@ -586,9 +586,9 @@
   parser.add_argument('--variations-test-seed-path',
                       type=os.path.relpath,
                       default=None,
-                      help='Path to a JSON file that contains the '
-                      'variations test seed. Defaults to running CTS tests '
-                      'without a variations test seed.')
+                      help=('Path to a JSON file that contains the '
+                            'variations test seed. Defaults to running CTS '
+                            'tests without a variations test seed.'))
   # We are re-using this argument that is used by our test runner
   # to detect if we are testing against an instant app
   # This allows us to know if we should filter tests based off the app
@@ -600,18 +600,17 @@
   parser.add_argument(
       _TEST_APK_AS_INSTANT_ARG,
       action='store_true',
-      help='Run CTS tests in instant app mode. '
-      'Instant apps run in a more restrictive execution environment.')
+      help=('Run CTS tests in instant app mode. '
+            'Instant apps run in a more restrictive execution environment.'))
 
   # Read the package name from the apk path passed by this flag to
   # determine if the emulator will start with "writable_system" or not.
   parser.add_argument(_USE_WEBVIEW_PROVIDER_ARG,
                       type=os.path.realpath,
                       default=None,
-                      help='Use this apk as the webview provider during test. '
-                      'The original provider will be restored if possible, '
-                      "on Nougat the provider can't be determined and so "
-                      'the system will choose the default provider.')
+                      help=('Use this apk as the webview provider during test. '
+                            'The original provider will be restored if '
+                            'possible.'))
 
 
   test_filter.AddFilterOptions(parser)
@@ -630,8 +629,8 @@
 
     if (args.test_filter_files or args.test_filters
         or args.isolated_script_test_filters):
-      # TODO(aluo): auto-determine the module based on the test filter and the
-      # available tests in each module
+      # TODO(https://crbug.com/40617687): auto-determine the module based on the
+      # test filter and the available tests in each module
       if not args.module_apk:
         args.module_apk = 'CtsWebkitTestCases.apk'
 
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index 7ef038c..86d6ced9 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # Used to cause full rebuilds on libc++ rolls. This should be kept in sync
   # with the libcxx_revision var in //DEPS.
-  libcxx_revision = "bb79a34585926c20974cd7191b730ac94133a992"
+  libcxx_revision = "917609c669e43edc850eeb192a342434a54e1dfd"
 }
diff --git a/chrome/VERSION b/chrome/VERSION
index a446282..970401f 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=137
 MINOR=0
-BUILD=7145
+BUILD=7146
 PATCH=0
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java
index bdae847..7bd5868 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiUtils.java
@@ -316,6 +316,9 @@
             String tabGroupDisplayName,
             @CollaborationServiceShareOrManageEntryPoint int entry) {
         Tab tab = filter.getTabModel().getTabById(tabId);
+        // The tab may have been closed in parallel with the share starting. Skip if this happens.
+        if (tab == null) return;
+
         LocalTabGroupId localTabGroupId = TabGroupSyncUtils.getLocalTabGroupId(tab);
 
         dataSharingTabManager.createOrManageFlow(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index 46ee0cc..ad61f0d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -658,7 +658,9 @@
                 new DefaultCustomTabIntentHandlingStrategy(
                         getCustomTabActivityTabProvider(),
                         getCustomTabActivityNavigationController(),
-                        getCustomTabObserver());
+                        getCustomTabObserver(),
+                        getVerifier(),
+                        getCurrentPageVerifier());
         if (getActivityType() == ActivityType.TRUSTED_WEB_ACTIVITY
                 || getActivityType() == ActivityType.WEB_APK) {
             TwaSharingController controller =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index 74dbcb5..aa31190 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -647,6 +647,12 @@
     }
 
     @Override
+    protected boolean shouldAllowThemingOnTablets() {
+        return mActivityType == ActivityType.TRUSTED_WEB_ACTIVITY
+                || mActivityType == ActivityType.WEB_APK;
+    }
+
+    @Override
     public void onPreInflationStartup() {
         super.onPreInflationStartup();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/DefaultCustomTabIntentHandlingStrategy.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/DefaultCustomTabIntentHandlingStrategy.java
index 5b4392c..27b1f672 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/DefaultCustomTabIntentHandlingStrategy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/DefaultCustomTabIntentHandlingStrategy.java
@@ -10,6 +10,8 @@
 import androidx.browser.trusted.FileHandlingData;
 
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
+import org.chromium.chrome.browser.browserservices.ui.controller.CurrentPageVerifier;
+import org.chromium.chrome.browser.browserservices.ui.controller.Verifier;
 import org.chromium.chrome.browser.customtabs.CustomTabAuthUrlHeuristics;
 import org.chromium.chrome.browser.customtabs.CustomTabObserver;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -27,14 +29,20 @@
     private final CustomTabActivityTabProvider mTabProvider;
     private final CustomTabActivityNavigationController mNavigationController;
     private final CustomTabObserver mCustomTabObserver;
+    private final Verifier mVerifier;
+    private final CurrentPageVerifier mCurrentPageVerfier;
 
     public DefaultCustomTabIntentHandlingStrategy(
             CustomTabActivityTabProvider tabProvider,
             CustomTabActivityNavigationController navigationController,
-            CustomTabObserver customTabObserver) {
+            CustomTabObserver customTabObserver,
+            Verifier verifier,
+            CurrentPageVerifier currentPageVerfier) {
         mTabProvider = tabProvider;
         mNavigationController = navigationController;
         mCustomTabObserver = customTabObserver;
+        mVerifier = verifier;
+        mCurrentPageVerfier = currentPageVerfier;
     }
 
     @Override
@@ -106,9 +114,23 @@
 
         if (launchHandler.getStartNewNavigation() && !isInitialIntent) {
             loadUrl(intentDataProvider);
+        } else {
+            // Check if the URL of the current page is in the web app scope.
+            // Launch params should not be sent to a not verified origin.
+            CurrentPageVerifier.VerificationState state = mCurrentPageVerfier.getState();
+            if (state == null || state.status != CurrentPageVerifier.VerificationStatus.SUCCESS) {
+                return;
+            }
         }
 
-        launchHandler.notifyLaunchQueue(mTabProvider.getTab().getWebContents());
+        // Check if the URL sent in launch params is in the web app scope.
+        mVerifier
+                .verify(intentDataProvider.getUrlToLoad())
+                .then(
+                        (verified) -> {
+                            if (!verified) return;
+                            launchHandler.notifyLaunchQueue(mTabProvider.getTab().getWebContents());
+                        });
     }
 
     private void loadUrl(BrowserServicesIntentDataProvider intentDataProvider) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index 0658369..9ad9d10 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -499,7 +499,8 @@
                         activityThemeColorSupplier,
                         isTablet,
                         shouldAllowThemingInNightMode(),
-                        shouldAllowBrightThemeColors());
+                        shouldAllowBrightThemeColors(),
+                        shouldAllowThemingOnTablets());
 
         mStatusBarColorController =
                 new StatusBarColorController(
@@ -1711,6 +1712,14 @@
     }
 
     /**
+     * Whether the top toolbar theme color provider should allow using a web page theme on large
+     * form-factors.
+     */
+    protected boolean shouldAllowThemingOnTablets() {
+        return false;
+    }
+
+    /**
      * Initialize the {@link BottomSheetController}. The view for this component is not created
      * until content is requested in the sheet.
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordManagerDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordManagerDialogTest.java
index 32b73b0..f618132 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordManagerDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordManagerDialogTest.java
@@ -21,7 +21,6 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
-import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,9 +37,10 @@
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
-import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
+import org.chromium.chrome.test.transit.AutoResetCtaTransitTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -60,21 +60,20 @@
 
     @Mock private Callback<Integer> mOnClick;
 
-    @ClassRule
-    public static ChromeTabbedActivityTestRule sActivityTestRule =
-            new ChromeTabbedActivityTestRule();
-
     @Rule
-    public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
-            new BlankCTATabInitialStateRule(sActivityTestRule, false);
+    public AutoResetCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.fastAutoResetCtaActivityRule();
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
 
+    private WebPageStation mStartingPage;
+
     @Before
     public void setUp() throws InterruptedException {
+        mStartingPage = mActivityTestRule.startOnBlankPage();
+        ChromeActivity activity = mStartingPage.getActivity();
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
-                    ChromeActivity activity = (ChromeActivity) sActivityTestRule.getActivity();
                     ModalDialogManager dialogManager = activity.getModalDialogManager();
                     mCoordinator =
                             new PasswordManagerDialogCoordinator(
@@ -150,7 +149,7 @@
     @SmallTest
     public void testWatchingLayoutChanges() {
         float dipScale =
-                sActivityTestRule.getActivity().getWindowAndroid().getDisplay().getDipScale();
+                mActivityTestRule.getActivity().getWindowAndroid().getDisplay().getDipScale();
 
         // Dimensions resembling landscape orientation.
         final int testHeightDipLandscape = 300; // Height of the android content view.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordSavingIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordSavingIntegrationTest.java
index 5dd442f..3e707cd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordSavingIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/PasswordSavingIntegrationTest.java
@@ -28,8 +28,10 @@
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
@@ -57,7 +59,8 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "show-autofill-signatures"})
 public class PasswordSavingIntegrationTest {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule public SigninTestRule mSigninTestRule = new SigninTestRule();
 
@@ -77,6 +80,7 @@
     private static final String CHANGE_PASSWORD_BUTTON_ID = "chg_submit_button";
     private static final String PASSWORD_MANAGER_ANNOTATION = "pm_parser_annotation";
 
+    private WebPageStation mStartingPage;
     private PasswordStoreBridge mPasswordStoreBridge;
     private BottomSheetController mBottomSheetController;
     private BottomSheetTestSupport mBottomSheetTestSupport;
@@ -85,7 +89,7 @@
 
     @Before
     public void setup() throws Exception {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mStartingPage = mActivityTestRule.startOnBlankPage();
         PasswordManagerTestHelper.setAccountForPasswordStore(SigninTestRule.TEST_ACCOUNT_EMAIL);
         PasswordManagerTestUtilsBridge.disableServerPredictions();
         mSigninTestRule.addAccountThenSignin(TestAccounts.ACCOUNT1);
@@ -100,7 +104,7 @@
                             new PasswordStoreBridge(mActivityTestRule.getProfile(false));
                 });
 
-        mWebContents = mActivityTestRule.getWebContents();
+        mWebContents = mStartingPage.webContentsElement.get();
         ImeAdapter imeAdapter = WebContentsUtils.getImeAdapter(mWebContents);
         mInputMethodManagerWrapper = TestInputMethodManagerWrapper.create(imeAdapter);
         imeAdapter.setInputMethodManagerWrapper(mInputMethodManagerWrapper);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/TouchToFillMainFlowIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/TouchToFillMainFlowIntegrationTest.java
index 3b807fb65..bda81c3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/TouchToFillMainFlowIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/TouchToFillMainFlowIntegrationTest.java
@@ -25,8 +25,10 @@
 import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerProvider;
@@ -60,10 +62,13 @@
     private PasswordStoreBridge mPasswordStoreBridge;
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule public SigninTestRule mSigninTestRule = new SigninTestRule();
 
+    private WebPageStation mStartingPage;
+
     public TouchToFillMainFlowIntegrationTest() {
         // This test suite relies on the real password store. However, that can only store
         // passwords if the device it runs on has the required min GMS Core version.
@@ -74,7 +79,7 @@
 
     @Before
     public void setUp() {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mStartingPage = mActivityTestRule.startOnBlankPage();
         PasswordManagerTestHelper.setAccountForPasswordStore(SigninTestRule.TEST_ACCOUNT_EMAIL);
         PasswordManagerTestUtilsBridge.disableServerPredictions();
         mSigninTestRule.addAccountThenSignin(TestAccounts.ACCOUNT1);
@@ -88,10 +93,10 @@
                 () -> {
                     mBottomSheetController =
                             BottomSheetControllerProvider.from(
-                                    mActivityTestRule.getActivity().getWindowAndroid());
+                                    mStartingPage.getActivity().getWindowAndroid());
                 });
 
-        mWebContents = mActivityTestRule.getWebContents();
+        mWebContents = mStartingPage.webContentsElement.get();
 
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/VirtualViewStructureInstrumentationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/VirtualViewStructureInstrumentationTest.java
index a9370650..55ec330 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/VirtualViewStructureInstrumentationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/VirtualViewStructureInstrumentationTest.java
@@ -13,14 +13,15 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.autofill.AutofillFeatures;
 import org.chromium.components.prefs.PrefService;
 import org.chromium.components.user_prefs.UserPrefs;
@@ -34,7 +35,8 @@
             "autofill.using_virtual_view_structure";
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private PrefService getPrefService() {
         return UserPrefs.get(mActivityTestRule.getProfile(false));
@@ -46,10 +48,7 @@
     @EnableFeatures({AutofillFeatures.AUTOFILL_VIRTUAL_VIEW_STRUCTURE_ANDROID})
     @DisabledTest(message = "https://crbug.com/1510968")
     public void testLogs3PModeDisabledMetrics() {
-        mActivityTestRule.startMainActivityOnBlankPage();
-        CriteriaHelper.pollUiThread(
-                mActivityTestRule.getActivity().getTabModelSelectorSupplier().get()
-                        ::isTabStateInitialized);
+        WebPageStation page = mActivityTestRule.startOnBlankPage();
 
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
@@ -61,10 +60,7 @@
                         "Autofill.ThirdPartyModeDisabled.Provider", 1);
 
         // A new tab opens because the metric is logged upon tab creation.
-        mActivityTestRule.loadUrlInNewTab(null);
-        CriteriaHelper.pollUiThread(
-                mActivityTestRule.getActivity().getTabModelSelectorSupplier().get()
-                        ::isTabStateInitialized);
+        page.openNewTabFast();
 
         histogramWatcher.assertExpected();
     }
@@ -74,10 +70,7 @@
     @MinAndroidSdkLevel(value = 28)
     @EnableFeatures({AutofillFeatures.AUTOFILL_VIRTUAL_VIEW_STRUCTURE_ANDROID})
     public void testDoesntLog3PModeDisabledMetricsWhen3PModeEnabled() {
-        mActivityTestRule.startMainActivityOnBlankPage();
-        CriteriaHelper.pollUiThread(
-                mActivityTestRule.getActivity().getTabModelSelectorSupplier().get()
-                        ::isTabStateInitialized);
+        WebPageStation page = mActivityTestRule.startOnBlankPage();
 
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
@@ -90,10 +83,7 @@
                         .build();
 
         // A new tab opens because the metric is logged upon tab creation.
-        mActivityTestRule.loadUrlInNewTab(null);
-        CriteriaHelper.pollUiThread(
-                mActivityTestRule.getActivity().getTabModelSelectorSupplier().get()
-                        ::isTabStateInitialized);
+        page.openNewTabFast();
 
         histogramWatcher.assertExpected();
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
index 299bee5..2a8ad44 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderTest.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.payments;
 
 import androidx.annotation.Nullable;
-import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.MediumTest;
 
 import org.junit.Assert;
@@ -23,7 +22,9 @@
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.payments.AndroidPaymentAppFinder;
 import org.chromium.components.payments.AppCreationFailureReason;
 import org.chromium.components.payments.CSPChecker;
@@ -40,7 +41,6 @@
 import org.chromium.components.payments.PaymentManifestWebDataService;
 import org.chromium.content_public.browser.RenderFrameHost;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.payments.mojom.PaymentDetailsModifier;
 import org.chromium.payments.mojom.PaymentItem;
 import org.chromium.payments.mojom.PaymentMethodData;
@@ -66,7 +66,8 @@
 public class AndroidPaymentAppFinderTest
         implements PaymentAppFactoryDelegate, PaymentAppFactoryParams {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     /** Simulates a package manager in memory. */
     private final MockPackageManagerDelegate mPackageManager = new MockPackageManagerDelegate();
@@ -113,8 +114,7 @@
     }
 
     private final TestServerDownloader mDownloader = new TestServerDownloader();
-
-    private EmbeddedTestServer mServer;
+    private WebPageStation mStartingPage;
     private List<PaymentApp> mPaymentApps;
     private boolean mAllPaymentAppsCreated;
     private Map<String, PaymentMethodData> mMethodData;
@@ -243,12 +243,13 @@
 
     @Before
     public void setUp() throws Throwable {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mStartingPage = mActivityTestRule.startOnBlankPage();
         mPackageManager.reset();
-        mServer =
-                EmbeddedTestServer.createAndStartServer(
-                        ApplicationProvider.getApplicationContext());
-        mDownloader.setTestServerUrl(new GURL(mServer.getURL("/components/test/data/payments/")));
+        mDownloader.setTestServerUrl(
+                new GURL(
+                        mActivityTestRule
+                                .getTestServer()
+                                .getURL("/components/test/data/payments/")));
         mPaymentApps = new ArrayList<>();
         mAllPaymentAppsCreated = false;
         mPaymentOptions = new PaymentOptions();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/ExpandablePaymentHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/ExpandablePaymentHandlerTest.java
index f96a065..46d71c5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/ExpandablePaymentHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/ExpandablePaymentHandlerTest.java
@@ -38,15 +38,17 @@
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.payments.handler.PaymentHandlerContentFrameLayout;
 import org.chromium.chrome.browser.payments.handler.PaymentHandlerCoordinator;
 import org.chromium.chrome.browser.payments.handler.PaymentHandlerCoordinator.PaymentHandlerUiObserver;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.payments.ui.InputProtector;
@@ -74,14 +76,16 @@
     private static final long SAFE_INPUT_DELAY =
             InputProtector.POTENTIALLY_UNINTENDED_INPUT_THRESHOLD;
 
-    @Rule public ChromeTabbedActivityTestRule mRule = new ChromeTabbedActivityTestRule();
+    @Rule
+    public FreshCtaTransitTestRule mRule = ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     // Host the tests on https://127.0.0.1, because file:// URLs cannot have service workers.
     private EmbeddedTestServer mServer;
+    private WebPageStation mStartingPage;
     private boolean mUiShownCalled;
     private boolean mUiClosedCalled;
     private UiDevice mDevice;
-    private ChromeActivity mDefaultActivity;
+    private ChromeTabbedActivity mDefaultActivity;
     private BottomSheetTestSupport mBottomSheetTestSupport;
     private FakeClock mClock;
 
@@ -127,12 +131,12 @@
 
     @Before
     public void setUp() throws Throwable {
-        mRule.startMainActivityOnBlankPage();
+        mStartingPage = mRule.startOnBlankPage();
         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        mDefaultActivity = mRule.getActivity();
+        mDefaultActivity = mStartingPage.getActivity();
         mBottomSheetTestSupport =
                 new BottomSheetTestSupport(
-                        mRule.getActivity()
+                        mDefaultActivity
                                 .getRootUiCoordinatorForTesting()
                                 .getBottomSheetController());
         mClock = new FakeClock();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/IsReadyToPayServiceHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/IsReadyToPayServiceHelperTest.java
index 041c823..36416028 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/IsReadyToPayServiceHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/IsReadyToPayServiceHelperTest.java
@@ -37,17 +37,12 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.components.payments.intent.IsReadyToPayServiceHelper;
 
 /** Tests for IsReadyToPayServiceHelper. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class IsReadyToPayServiceHelperTest {
-    @Rule
-    public final ChromeTabbedActivityTestRule mActivityTestRule =
-            new ChromeTabbedActivityTestRule();
-
     @Rule public final ExpectedException mExpectedExceptionRule = ExpectedException.none();
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentDetailsUpdateServiceHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentDetailsUpdateServiceHelperTest.java
index ed6336a..b213873f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentDetailsUpdateServiceHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentDetailsUpdateServiceHelperTest.java
@@ -31,7 +31,8 @@
 import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.components.payments.Address;
 import org.chromium.components.payments.ErrorStrings;
 import org.chromium.components.payments.IPaymentDetailsUpdateService;
@@ -58,7 +59,8 @@
     private static final int DECODER_STARTUP_TIMEOUT_IN_MS = 10000;
 
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     @Rule public ExpectedException thrown = ExpectedException.none();
 
@@ -108,7 +110,7 @@
 
     @Before
     public void setUp() throws Throwable {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        mActivityTestRule.startOnBlankPage();
         mContext = mActivityTestRule.getActivity();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
index 0723de4..f59311c 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestDownloaderTest.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.payments;
 
-import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.MediumTest;
 
 import org.hamcrest.Matchers;
@@ -23,7 +22,9 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.payments.CSPChecker;
 import org.chromium.components.payments.PaymentManifestDownloader;
 import org.chromium.components.payments.PaymentManifestDownloader.ManifestDownloadCallback;
@@ -37,7 +38,8 @@
 @MediumTest
 public class PaymentManifestDownloaderTest implements ManifestDownloadCallback {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private static final String EXPECTED_PAYMENT_METHOD_MANIFEST =
             "{\n" + "  \"default_applications\": [\"https://bobpay.test/app.json\"]\n" + "}\n";
@@ -98,14 +100,12 @@
 
     @Before
     public void setUp() throws Throwable {
-        mActivityTestRule.startMainActivityOnBlankPage();
-        mServer =
-                EmbeddedTestServer.createAndStartServer(
-                        ApplicationProvider.getApplicationContext());
+        mServer = mActivityTestRule.getTestServer();
+        WebPageStation page = mActivityTestRule.startOnBlankPage();
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     mDownloader.initialize(
-                            mActivityTestRule.getActivity().getCurrentWebContents(),
+                            page.webContentsElement.get(),
                             new CSPChecker() {
                                 @Override
                                 public void allowConnectToSource(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestParserTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestParserTest.java
index 530796ed..1e064f406 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestParserTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentManifestParserTest.java
@@ -19,7 +19,9 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.transit.ChromeTransitTestRules;
+import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
+import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.components.payments.PaymentManifestParser;
 import org.chromium.components.payments.PaymentManifestParser.ManifestParseCallback;
 import org.chromium.components.payments.WebAppManifestSection;
@@ -30,7 +32,8 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class PaymentManifestParserTest implements ManifestParseCallback {
     @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
+    public FreshCtaTransitTestRule mActivityTestRule =
+            ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private final PaymentManifestParser mParser = new PaymentManifestParser();
     private GURL[] mWebAppManifestUris;
@@ -61,9 +64,9 @@
 
     @Before
     public void setUp() throws Throwable {
-        mActivityTestRule.startMainActivityOnBlankPage();
+        WebPageStation page = mActivityTestRule.startOnBlankPage();
         ThreadUtils.runOnUiThreadBlocking(
-                () -> mParser.createNative(mActivityTestRule.getWebContents()));
+                () -> mParser.createNative(page.webContentsElement.get()));
         mWebAppManifestUris = null;
         mSupportedOrigins = null;
         mWebAppManifest = null;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
index 41ed5ff..c9108dac3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
@@ -24,6 +24,7 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.emptyIterable;
@@ -3305,6 +3306,95 @@
                 settingsActivity, "site_settings_rws_grouped_website_settings_delete_dialog");
     }
 
+    @Test
+    @SmallTest
+    public void deleteSingleSiteDataRemovesRowSingleWebsiteSettings() throws Exception {
+        final String currentSiteUrl = "one-test.com";
+        final String rwsMemberUrl = "two-test.com";
+        final SettingsActivity settingsActivity =
+                SiteSettingsTestUtils.startSingleWebsitePreferences(
+                        getRwsOwnerSiteForUrls(currentSiteUrl, rwsMemberUrl));
+
+        onView(
+                        allOf(
+                                withText(currentSiteUrl),
+                                isDescendantOfA(withId(SingleWebsiteSettings.RWS_ROW_ID))))
+                .check(matches(isDisplayed()));
+        onView(
+                        allOf(
+                                withText(rwsMemberUrl),
+                                isDescendantOfA(withId(SingleWebsiteSettings.RWS_ROW_ID))))
+                .check(matches(isDisplayed()));
+
+        onView(
+                        allOf(
+                                withId(R.id.image_view_widget),
+                                isDescendantOfA(withId(SingleWebsiteSettings.RWS_ROW_ID)),
+                                withContentDescription(containsString(rwsMemberUrl))))
+                .check(matches(isDisplayed()))
+                .perform(click());
+        onView(withText(R.string.website_reset_confirmation)).check(matches(isDisplayed()));
+        onView(withText(R.string.website_reset))
+                .inRoot(isDialog())
+                .check(matches(isDisplayed()))
+                .perform(click());
+        onView(
+                        allOf(
+                                withText(rwsMemberUrl),
+                                isDescendantOfA(withId(SingleWebsiteSettings.RWS_ROW_ID))))
+                .check(doesNotExist());
+        onView(
+                        allOf(
+                                withText(currentSiteUrl),
+                                isDescendantOfA(withId(SingleWebsiteSettings.RWS_ROW_ID))))
+                .check(matches(isDisplayed()));
+    }
+
+    @Test
+    @SmallTest
+    public void deleteSingleSiteDataRemovesRowGroupedWebsiteSettings() throws Exception {
+        final String currentSiteUrl = "one-test.com";
+        final String rwsMemberUrl = "two-test.com";
+
+        final SettingsActivity settingsActivity =
+                SiteSettingsTestUtils.startGroupedWebsitesPreferences(
+                        getRwsSiteGroupForUrls(currentSiteUrl, rwsMemberUrl));
+
+        onView(
+                        allOf(
+                                withText(currentSiteUrl),
+                                isDescendantOfA(withId(GroupedWebsitesSettings.RWS_ROW_ID))))
+                .check(matches(isDisplayed()));
+        onView(
+                        allOf(
+                                withText(rwsMemberUrl),
+                                isDescendantOfA(withId(GroupedWebsitesSettings.RWS_ROW_ID))))
+                .check(matches(isDisplayed()));
+
+        onView(
+                        allOf(
+                                withId(R.id.image_view_widget),
+                                isDescendantOfA(withId(GroupedWebsitesSettings.RWS_ROW_ID)),
+                                withContentDescription(containsString(rwsMemberUrl))))
+                .check(matches(isDisplayed()))
+                .perform(click());
+        onView(withText(R.string.website_reset_confirmation)).check(matches(isDisplayed()));
+        onView(withText(R.string.website_reset))
+                .inRoot(isDialog())
+                .check(matches(isDisplayed()))
+                .perform(click());
+        onView(
+                        allOf(
+                                withText(rwsMemberUrl),
+                                isDescendantOfA(withId(GroupedWebsitesSettings.RWS_ROW_ID))))
+                .check(doesNotExist());
+        onView(
+                        allOf(
+                                withText(currentSiteUrl),
+                                isDescendantOfA(withId(GroupedWebsitesSettings.RWS_ROW_ID))))
+                .check(matches(isDisplayed()));
+    }
+
     // TODO(crbug.com/396463421): Remove once RWS UI V2 launched.
     @Test
     @SmallTest
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
index 93358f1..fee1e3f6 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityContentTestEnvironment.java
@@ -27,6 +27,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.IntentUtils;
+import org.chromium.base.Promise;
 import org.chromium.base.UserDataHost;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.chrome.browser.ActivityTabProvider;
@@ -37,6 +38,8 @@
 import org.chromium.chrome.browser.app.tabmodel.CustomTabsTabModelOrchestrator;
 import org.chromium.chrome.browser.browserservices.intents.ColorProvider;
 import org.chromium.chrome.browser.browserservices.intents.SessionHolder;
+import org.chromium.chrome.browser.browserservices.ui.controller.CurrentPageVerifier;
+import org.chromium.chrome.browser.browserservices.ui.controller.Verifier;
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.content.WebContentsFactory;
 import org.chromium.chrome.browser.content.WebContentsFactoryJni;
@@ -115,6 +118,8 @@
     @Mock private Profile mIncognitoProfile;
     @Mock CustomTabAuthUrlHeuristics.Natives mCustomTabAuthUrlHeuristicsJniMock;
     @Mock WebContentsFactory.Natives mWebContentsFactoryJni;
+    @Mock public Verifier verifier;
+    @Mock public CurrentPageVerifier currentPageVerifier;
 
     public final CustomTabActivityTabProvider tabProvider =
             new CustomTabActivityTabProvider(SPECULATED_URL);
@@ -164,6 +169,11 @@
         when(profileProvider.getOriginalProfile()).thenReturn(mProfile);
         when(profileProvider.getOffTheRecordProfile(eq(true))).thenReturn(mIncognitoProfile);
         when(mIncognitoProfile.isOffTheRecord()).thenReturn(true);
+        when(verifier.verify(any())).thenReturn(Promise.fulfilled(true));
+        when(currentPageVerifier.getState())
+                .thenReturn(
+                        new CurrentPageVerifier.VerificationState(
+                                "", "", CurrentPageVerifier.VerificationStatus.SUCCESS));
     }
 
     @Override
@@ -216,7 +226,11 @@
                 tabProvider,
                 intentDataProvider,
                 new DefaultCustomTabIntentHandlingStrategy(
-                        tabProvider, navigationController, customTabObserver),
+                        tabProvider,
+                        navigationController,
+                        customTabObserver,
+                        verifier,
+                        currentPageVerifier),
                 activity,
                 mMinimizationManagerHolder);
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java
index 90ac155..4a52c1b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java
@@ -13,6 +13,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
 
 import static org.chromium.chrome.browser.customtabs.content.CustomTabActivityContentTestEnvironment.CONTENT_URI;
 import static org.chromium.chrome.browser.customtabs.content.CustomTabActivityContentTestEnvironment.INITIAL_URL;
@@ -21,6 +22,7 @@
 
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Looper;
 
 import androidx.browser.trusted.FileHandlingData;
 import androidx.browser.trusted.LaunchHandlerClientMode;
@@ -202,6 +204,7 @@
             int expectedLoadUrlNumber,
             boolean expectedStartNewNavigation) {
         mIntentHandler.onNewIntent(intentDataProvider);
+        shadowOf(Looper.getMainLooper()).idle();
         verify(env.tabFromFactory, times(expectedLoadUrlNumber))
                 .loadUrl(argThat(params -> OTHER_URL.equals(params.getUrl())));
         verify(mWebAppLaunchHandlerJniMock, times(1))
@@ -301,6 +304,7 @@
         when(intentDataProvider.getFileHandlingData()).thenReturn(createFileHandlingData());
 
         mIntentHandler.onNewIntent(intentDataProvider);
+        shadowOf(Looper.getMainLooper()).idle();
         verify(env.tabFromFactory, times(1))
                 .loadUrl(argThat(params -> OTHER_URL.equals(params.getUrl())));
         verify(mWebAppLaunchHandlerJniMock, times(1))
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index a1d6baf..2e5d2a89 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -1182,8 +1182,8 @@
         <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_NO_UNSYNCED_DATA_BODY" desc="This is the subtitle of a confirmation dialog that asks the user to confirm that they want to sign out of Chromium. It appears when the user opens the Chromium Profile Menu and chooses 'Sign out of Chromium'. This subtitle is after the 'Sign out of Chromium?' string. The tone should be calm, because we want to explain to the user that their data is saved; however, we want to set expectations by clarifying that the data will be removed from the device. This string provides a path to resolution because it explains how the user can see their data again (by signing back in).">
           Your passwords and other Chromium data that you saved in your Google Account will be removed from this device. To use them again in Chromium, sign back in.
         </message>
-        <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY" desc="This is the subtitle of an error message dialog that appears when the user opens the Chromium Profile Menu and chooses 'Sign out of Chromium'. The error message appears when the user has saved some data (like a password or payment method) to Chromium, but Chromium wasn't able to save the data to the user's Google Account due to a sign-in error; but, the data has been saved locally to Chromium. This subtitle is after the 'Some data isn’t saved yet' string. The tone should be cautionary, because we want to explain to the user that their data is not saved to their Google Account. This string provides a path to resolution because it explains how the user can properly save their data before they sign out of Chromium.">
-          Chromium needs to verify it’s you before some data can be saved in your Google Account and used on all your devices. If you sign out, this data will stay on this device.
+        <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY" desc="This is the subtitle of an error message dialog that appears when the user opens the Chromium Profile Menu and chooses 'Sign out of Chromium'. The error message appears when the user has saved some data (like a password or payment method) to Chromium, but Chromium wasn't able to save the data to the user's Google Account due to a sign-in error; we want to set expectations by clarifying that the data will be removed from the device. This subtitle is after the 'Some data isn’t saved yet' string. The tone should be cautionary, because we want to explain to the user that their data is not saved and will be deleted if they sign out. This string provides a path to resolution because it explains how the user can properly save their data before they sign out of Chromium.">
+          Chromium needs to verify it’s you before some data can be saved in your Google Account and used on all your devices. If you sign out now, this data will be deleted.
         </message>
         <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_UNSYNCED_BODY"  desc="This is the subtitle of an error message dialog that appears when the user opens the Chromium Profile Menu and chooses 'Sign out of Chromium'. The error message appears when the user tries to sign out immediately after having saved saved some data (like a password or payment method) to Chromium and Chromium hasn't had time to save that data to the user's Google Account yet. This subtitle is after the 'Some data isn't saved yet' string. The tone should be cautionary, because we want to explain to the user that their data is not saved to their Google Account. This string asks the user to wait a few minutes before trying to sign out again.">
           Some of your Chromium data hasn't been saved in your Google Account yet. Try waiting a few minutes before signing out. If you sign out now, this data will be deleted.
diff --git a/chrome/app/chromium_strings_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY.png.sha1 b/chrome/app/chromium_strings_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY.png.sha1
index 1193608..9d35c85 100644
--- a/chrome/app/chromium_strings_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY.png.sha1
+++ b/chrome/app/chromium_strings_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY.png.sha1
@@ -1 +1 @@
-aa6e5510da0ea2606313ea1b00e41aef7117bfe6
\ No newline at end of file
+99a6aeca5ccf4d39e214f66232847dbd131f65a4
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index f1148ea5..7dd367f 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -14167,9 +14167,6 @@
         <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_TITLE"  desc="This is the title of an error message dialog that appears when the user opens the Chrome Profile Menu and chooses 'Sign out of Chrome'. The error message appears when the user has saved some data (like a password or payment method) to Chrome, but Chrome wasn't able to save the data to the user's Google Account due to a sign-in error; but, the data has been saved locally to Chrome. This title is before the 'Chrome needs to verify it’s you before some data can be saved in your Google Account and used on all your devices. If you sign out, this data will stay on this device.' string. The tone should be cautionary.">
           Some data isn’t saved in your account yet
         </message>
-        <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_SIGNOUT_BUTTON" desc="This string is found on a button that lets the user sign out of Chrome when the user taps it. It is on an error message dialog that appears when the user opens the Chrome Profile Menu and chooses 'Sign out of Chrome'. The error message appears when the user has saved some data (like a password or payment method) to Chrome, but Chrome wasn't able to save the data to the user's Google Account due to a sign-in error; but, the data has been saved locally to Chrome. This text is a verb and is short for the longer phrase 'Sign out of Chrome anyway.'">
-          Sign out anyway
-        </message>
         <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_DELETE_AND_SIGNOUT_BUTTON" desc="This string is found on a button that lets the user sign out of Chrome when the user taps it. It is on an error message dialog that appears when the user opens the Chrome Profile Menu and chooses 'Sign out of Chrome'. The error message appears when the user has saved some data (like a password or payment method) to Chrome, but Chrome wasn't able to save the data to the user's Google Account due to a network error; but, the data has been saved locally to Chrome. This text is a verb and is short for the longer phrase 'Delete data and sign out of Chrome anyway.'">
           Delete and sign out
         </message>
diff --git a/chrome/app/generated_resources_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_SIGNOUT_BUTTON.png.sha1 b/chrome/app/generated_resources_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_SIGNOUT_BUTTON.png.sha1
deleted file mode 100644
index 707c548e..0000000
--- a/chrome/app/generated_resources_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_SIGNOUT_BUTTON.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-22fc6290b96c91e4764716a26a9afa336dd6292f
\ No newline at end of file
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index e8ab6a49..8572774 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -1136,8 +1136,8 @@
         <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_NO_UNSYNCED_DATA_BODY" desc="This is the subtitle of a confirmation dialog that asks the user to confirm that they want to sign out of Chrome. It appears when the user opens the Chrome Profile Menu and chooses 'Sign out of Chrome'. This subtitle is after the 'Sign out of Chrome?' string. The tone should be calm, because we want to explain to the user that their data is saved; however, we want to set expectations by clarifying that the data will be removed from the device. This string provides a path to resolution because it explains how the user can see their data again (by signing back in).">
           Your passwords and other Chrome data that you saved in your Google Account will be removed from this device. To use them again in Chrome, sign back in.
         </message>
-        <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY" desc="This is the subtitle of an error message dialog that appears when the user opens the Chrome Profile Menu and chooses 'Sign out of Chrome'. The error message appears when the user has saved some data (like a password or payment method) to Chrome, but Chrome wasn't able to save the data to the user's Google Account due to a sign-in error; but, the data has been saved locally to Chrome. This subtitle is after the 'Some data isn’t saved yet' string. The tone should be cautionary, because we want to explain to the user that their data is not saved to their Google Account. This string provides a path to resolution because it explains how the user can properly save their data before they sign out of Chrome.">
-          Chrome needs to verify it’s you before some data can be saved in your Google Account and used on all your devices. If you sign out, this data will stay on this device.
+        <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY" desc="This is the subtitle of an error message dialog that appears when the user opens the Chrome Profile Menu and chooses 'Sign out of Chrome'. The error message appears when the user has saved some data (like a password or payment method) to Chrome, but Chrome wasn't able to save the data to the user's Google Account due to a sign-in error; we want to set expectations by clarifying that the data will be removed from the device. This subtitle is after the 'Some data isn’t saved yet' string. The tone should be cautionary, because we want to explain to the user that their data is not saved and will be deleted if they sign out. This string provides a path to resolution because it explains how the user can properly save their data before they sign out of Chrome.">
+          Chrome needs to verify it’s you before some data can be saved in your Google Account and used on all your devices. If you sign out now, this data will be deleted.
         </message>
         <message name="IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_UNSYNCED_BODY" desc="This is the subtitle of an error message dialog that appears when the user opens the Chrome Profile Menu and chooses 'Sign out of Chrome'. The error message appears when the user tries to sign out immediately after having saved saved some data (like a password or payment method) to Chrome and Chrome hasn't had time to save that data to the user's Google Account yet. This subtitle is after the 'Some data isn't saved yet' string. The tone should be cautionary, because we want to explain to the user that their data is not saved to their Google Account. This string asks the user to wait a few minutes before trying to sign out again.">
           Some of your Chrome data hasn't been saved in your Google Account yet. Try waiting a few minutes before signing out. If you sign out now, this data will be deleted.
diff --git a/chrome/app/google_chrome_strings_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY.png.sha1
index f8fe6a6..9d35c85 100644
--- a/chrome/app/google_chrome_strings_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY.png.sha1
+++ b/chrome/app/google_chrome_strings_grd/IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY.png.sha1
@@ -1 +1 @@
-4a27bed30a5057760ddea3761e9c7d7638236df5
\ No newline at end of file
+99a6aeca5ccf4d39e214f66232847dbd131f65a4
\ No newline at end of file
diff --git a/chrome/app/profiles_strings.grdp b/chrome/app/profiles_strings.grdp
index 69a2141..fd3de08 100644
--- a/chrome/app/profiles_strings.grdp
+++ b/chrome/app/profiles_strings.grdp
@@ -1071,18 +1071,18 @@
     <!-- Signout Confirmation Dialog Desktop -->
     <message name="IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TITLE" desc="Title of the Extensions section in the Signout Confirmation dialog which allows users to remove extensions linked to their account when signing out.">
       {ACCOUNT_EXTENSIONS_COUNT, plural,
-      =1 {Remove 1 extension and the data it's saved on this device}
-      other {Remove {ACCOUNT_EXTENSIONS_COUNT} extensions and the data they've saved on this device}
+      =1 {Remove 1 extension and the data that it saved on this device}
+      other {Remove {ACCOUNT_EXTENSIONS_COUNT} extensions and the data that they saved on this device}
       }
     </message>
     <message name="IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TOOLTIP" desc="Mouse over tooltip for the Extensions section in the Signout Confirmation dialog which allows users to remove extensions linked to their account when signing out.">
       {ACCOUNT_EXTENSIONS_COUNT, plural,
-      =1 {This extension is saved in your Google Account, but any data it’s saved only on this device may be deleted}
+      =1 {This extension is saved in your Google Account, but any data it saved only on this device may be deleted}
       other {These extensions are saved in your Google Account, but any data they've saved only on this device may be deleted}
       }
     </message>
     <message name="IDS_SIGNOUT_CONFIRMATION_UNSYNCED_DATA_WITH_ACCOUNT_EXTENSIONS" desc="An additional message shown in the Signout Confirmation dialog if the user initiates a signout with unsynced data and they have account extensions installed.">
-      You can also choose to remove account extensions from this device.
+      You can also remove these extensions from this device:
     </message>
 
     <!-- History Sync Opt-in Desktop -->
diff --git a/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TITLE.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TITLE.png.sha1
index 64d6826..cc737ba6 100644
--- a/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TITLE.png.sha1
+++ b/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TITLE.png.sha1
@@ -1 +1 @@
-a70f9a931ad507d549fa2b7ead715c937d225637
\ No newline at end of file
+ff532326d3edf338cd5e9617f973832533937561
\ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TOOLTIP.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TOOLTIP.png.sha1
index 41788ef..88bc7fa 100644
--- a/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TOOLTIP.png.sha1
+++ b/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_EXTENSIONS_SECTION_TOOLTIP.png.sha1
@@ -1 +1 @@
-b49ab759ccfc8a83f38a2dcaef6a425224d2528e
\ No newline at end of file
+5aa7a59a44c12f681bd18d6212506fe416bc7159
\ No newline at end of file
diff --git a/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_UNSYNCED_DATA_WITH_ACCOUNT_EXTENSIONS.png.sha1 b/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_UNSYNCED_DATA_WITH_ACCOUNT_EXTENSIONS.png.sha1
index f269e967..c256863 100644
--- a/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_UNSYNCED_DATA_WITH_ACCOUNT_EXTENSIONS.png.sha1
+++ b/chrome/app/profiles_strings_grdp/IDS_SIGNOUT_CONFIRMATION_UNSYNCED_DATA_WITH_ACCOUNT_EXTENSIONS.png.sha1
@@ -1 +1 @@
-be00d3367e133ee486dabc90d5675bd5f064d420
\ No newline at end of file
+110abde525757cb6e5fa1f23a96927943b302ba2
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 4ebf0d7..0093824 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -625,7 +625,7 @@
   <message name="IDS_SETTINGS_AUTOFILL_AI_WHEN_ON_SAVED_INFO" desc="Bullet 1 of 2 that appears beneath the 'When on' title. 'Autofill': the user is on the 'Autofill with AI' feature page, but we want to frame this new type of autofill as just 'Autofill'. 'Can offer to save info when you submit forms': we want to convey that the user has the choice, each time a form is submitted, whether they want their info saved to Chrome for the purposes of Autofill. 'Like your driver's license or passport number': Today, supported data types for 'Autofill with AI' include 1) driver's licenses, 2) passports, and 3) vehicles. Eventually we'll support more, but this example needs to mention these types of data and not others.">
     Autofill can offer to save info when you submit forms, like your driver's license or passport number
   </message>
-  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_DATA_USAGE" desc="Bullet 1 of 2 that appears beneath the 'Things to consider' title. The point of this bullet is to convey to the user the types of data that get shared with Google to support this feature. There are 2 data types: 1) 'sites you visit' can mean browsing history and the URL of the page the user is visiting, and 2) 'page content', by which we mean the words, visuals, and layout of the pages the user visits. This notion that Google is considering 'page content' is new. We want to lead with 'To offer suggestions' even though it might be a bit grammatically awkward, because we want to express the need / value before explaining the data that gets shared.">
+  <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_DATA_USAGE" desc="Bullet 1 of 2 that appears beneath the 'Things to consider' title. The point of this bullet is to convey to the user the types of data that get shared with Google to support this feature. There are 2 data types: 1) 'the page’s URL' and 2) 'content', by which we mean the words, visuals, and layout of the pages the user visits. This notion that Google is considering 'page content' is new. We want to lead with 'To offer suggestions' even though it might be a bit grammatically awkward, because we want to express the need / value before explaining the data that gets shared.">
     To offer suggestions and improve this feature, the page’s URL and content are shared with Google
   </message>
   <message name="IDS_SETTINGS_AUTOFILL_AI_TO_CONSIDER_STORAGE" desc="Bullet 2 of 2 that appears beneath the 'Things to consider' title. This statement isn't immediately relevant to how Autofill with AI works, but it's intended as a reassuring statement.">
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 45db020..84ed82d5 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -5900,6 +5900,10 @@
      flag_descriptions::kWebRtcAllowWgcWindowCapturerName,
      flag_descriptions::kWebRtcAllowWgcWindowCapturerDescription, kOsWin,
      FEATURE_VALUE_TYPE(features::kWebRtcAllowWgcWindowCapturer)},
+    {"webrtc-wgc-require-border",
+     flag_descriptions::kWebRtcWgcRequireBorderName,
+     flag_descriptions::kWebRtcWgcRequireBorderDescription, kOsWin,
+     FEATURE_VALUE_TYPE(features::kWebRtcWgcRequireBorder)},
 #endif  // BUILDFLAG(IS_WIN)
 #if defined(TOOLKIT_VIEWS) || BUILDFLAG(IS_ANDROID)
     {"enable-autofill-credit-card-upload",
diff --git a/chrome/browser/ash/login/existing_user_controller.h b/chrome/browser/ash/login/existing_user_controller.h
index 9207b69..db0ab43 100644
--- a/chrome/browser/ash/login/existing_user_controller.h
+++ b/chrome/browser/ash/login/existing_user_controller.h
@@ -336,7 +336,7 @@
 
   // True if password has been changed for user who is completing sign in.
   // Set in OnLoginSuccess. Before that use LoginPerformer::password_changed().
-  bool password_changed_;
+  bool password_changed_ = false;
 
   // Set in OnLoginSuccess. Before that use LoginPerformer::auth_mode().
   // Initialized with `kExternal` as more restricted mode.
diff --git a/chrome/browser/ash/login/session/user_session_manager.cc b/chrome/browser/ash/login/session/user_session_manager.cc
index dec11f4..c4077dc 100644
--- a/chrome/browser/ash/login/session/user_session_manager.cc
+++ b/chrome/browser/ash/login/session/user_session_manager.cc
@@ -132,6 +132,7 @@
 #include "chromeos/ash/components/account_manager/account_manager_factory.h"
 #include "chromeos/ash/components/assistant/buildflags.h"
 #include "chromeos/ash/components/browser_context_helper/browser_context_flusher.h"
+#include "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
 #include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
 #include "chromeos/ash/components/dbus/dbus_thread_manager.h"
 #include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
@@ -370,7 +371,24 @@
   prefs->SetBoolean(::prefs::kLanguageShouldMergeInputMethods, true);
 }
 
-bool CanPerformEarlyRestart() {
+bool IsKioskProfile(Profile* profile) {
+  const user_manager::User* user =
+      ash::BrowserContextHelper::Get()->GetUserByBrowserContext(profile);
+  return user && user->IsKioskType();
+}
+
+bool AreKioskTroubleshootingToolsEnabled(Profile* profile) {
+  return profile->GetPrefs()->GetBoolean(
+      ::prefs::kKioskTroubleshootingToolsEnabled);
+}
+
+bool CanPerformEarlyRestart(Profile* profile) {
+  // Allow early restart in kiosk mode to apply flags for experimentation when
+  // the troubleshooting tools policy is set.
+  if (IsKioskProfile(profile) && AreKioskTroubleshootingToolsEnabled(profile)) {
+    return true;
+  }
+
   const ExistingUserController* controller =
       ExistingUserController::current_controller();
   if (!controller)
@@ -985,15 +1003,10 @@
 
   MaybeSaveSessionStartedTimeBeforeRestart(profile);
 
-  // Kiosk sessions keeps the startup flags.
-  if (user_manager::UserManager::Get() &&
-      user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) {
+  if (early_restart && !CanPerformEarlyRestart(profile)) {
     return false;
   }
 
-  if (early_restart && !CanPerformEarlyRestart())
-    return false;
-
   // We can't really restart if we've already restarted as a part of
   // user session restore after crash of in case when flags were changed inside
   // user session.
diff --git a/chrome/browser/ash/notifications/multi_capture_notifications.cc b/chrome/browser/ash/notifications/multi_capture_notifications.cc
index 5fc50977..de30b60 100644
--- a/chrome/browser/ash/notifications/multi_capture_notifications.cc
+++ b/chrome/browser/ash/notifications/multi_capture_notifications.cc
@@ -91,24 +91,6 @@
           IDS_MULTI_CAPTURE_NOTIFICATION_ON_LOGIN_MESSAGE));
 }
 
-// This function makes sure that on login all data required to check whether a
-// notification is needed is propagated from the policy to the
-// ManagedAccessToGetAllScreensMediaInSessionAllowedForUrls pref.
-void TransferGetAllScreensMediaPolicyValue(
-    content::BrowserContext* browser_context) {
-  DCHECK(browser_context);
-  Profile* profile = Profile::FromBrowserContext(browser_context);
-  PrefService* pref_service = profile->GetPrefs();
-  if (!pref_service) {
-    return;
-  }
-  const base::Value::List& allowed_origins = pref_service->GetList(
-      capture_policy::kManagedAccessToGetAllScreensMediaAllowedForUrls);
-  pref_service->SetList(
-      prefs::kManagedAccessToGetAllScreensMediaInSessionAllowedForUrls,
-      allowed_origins.Clone());
-}
-
 void ShowLoginNotificationIfMultiCaptureAllowed() {
   auto* active_user = user_manager::UserManager::Get()->GetActiveUser();
   if (!active_user) {
@@ -121,11 +103,8 @@
     return;
   }
 
-  // TODO(b/329064666): Remove this function once the pivot to IWAs is complete.
-  TransferGetAllScreensMediaPolicyValue(browser_context);
-
   capture_policy::CheckGetAllScreensMediaAllowedForAnyOrigin(
-      browser_context, base::BindOnce(&MaybeShowLoginNotification));
+      base::BindOnce(&MaybeShowLoginNotification));
 }
 
 }  // namespace
diff --git a/chrome/browser/autofill/valuables_data_manager_factory.cc b/chrome/browser/autofill/valuables_data_manager_factory.cc
index 9c311978..defd2a3 100644
--- a/chrome/browser/autofill/valuables_data_manager_factory.cc
+++ b/chrome/browser/autofill/valuables_data_manager_factory.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/autofill/valuables_data_manager_factory.h"
 
 #include "base/no_destructor.h"
+#include "chrome/browser/autofill/autofill_image_fetcher_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/autofill/core/browser/data_manager/valuables/valuables_data_manager.h"
@@ -32,6 +33,7 @@
           "AutofillValuablesDataManager",
           ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(WebDataServiceFactory::GetInstance());
+  DependsOn(AutofillImageFetcherFactory::GetInstance());
 }
 
 ValuablesDataManagerFactory::~ValuablesDataManagerFactory() = default;
@@ -44,6 +46,10 @@
     return nullptr;
   }
   Profile* profile = Profile::FromBrowserContext(context);
+  // The AutofillImageFetcherFactory redirects to the original profile.
+  AutofillImageFetcherBase* image_fetcher =
+      AutofillImageFetcherFactory::GetForProfile(profile);
+
   scoped_refptr<autofill::AutofillWebDataService> account_storage =
       WebDataServiceFactory::GetAutofillWebDataForAccount(
           profile, ServiceAccessType::EXPLICIT_ACCESS);
@@ -52,7 +58,8 @@
     // WebDataServiceFactory::ServiceIsNULLWhileTesting() is true.
     return nullptr;
   }
-  return std::make_unique<ValuablesDataManager>(std::move(account_storage));
+  return std::make_unique<ValuablesDataManager>(std::move(account_storage),
+                                                image_fetcher);
 }
 
 bool ValuablesDataManagerFactory::ServiceIsCreatedWithBrowserContext() const {
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 621296a..ee3bea758 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -2673,7 +2673,6 @@
     content::RenderFrameHost* render_frame_host,
     base::OnceCallback<void(bool)> callback) {
   capture_policy::CheckGetAllScreensMediaAllowed(
-      render_frame_host->GetBrowserContext(),
       render_frame_host->GetMainFrame()->GetLastCommittedOrigin().GetURL(),
       std::move(callback));
 }
diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc
index e5f855e..97868e0 100644
--- a/chrome/browser/extensions/component_loader.cc
+++ b/chrome/browser/extensions/component_loader.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/extensions/component_loader.h"
 
+#include <initializer_list>
 #include <optional>
 #include <string>
 #include <string_view>
@@ -64,6 +65,7 @@
 #include "ash/constants/ash_switches.h"
 #include "ash/keyboard/ui/grit/keyboard_resources.h"
 #include "base/system/sys_info.h"
+#include "chrome/browser/chromeos/extensions/component_extension_content_settings/component_extension_content_settings_allowlist.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "components/user_manager/user_manager.h"
 #include "content/public/browser/site_instance.h"
@@ -562,11 +564,18 @@
     Add(IDR_ECHO_MANIFEST,
         base::FilePath(FILE_PATH_LITERAL("/usr/share/chromeos-assets/echo")));
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+    std::initializer_list<ContentSettingsType> system_permissions = {
+        ContentSettingsType::FILE_SYSTEM_READ_GUARD,
+        ContentSettingsType::FILE_SYSTEM_WRITE_GUARD};
+
     AddComponentFromDirWithManifestFilename(
         base::FilePath("/usr/share/chromeos-assets/quickoffice"),
         extension_misc::kQuickOfficeComponentExtensionId,
         extensions::kManifestFilename, extensions::kManifestFilename,
-        base::DoNothing());
+        base::BindOnce(&ComponentLoader::GrantPermissions,
+                       weak_factory_.GetWeakPtr(),
+                       extension_misc::kQuickOfficeComponentExtensionId,
+                       std::move(system_permissions)));
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
@@ -724,6 +733,22 @@
   extensions::ProcessManager::Get(profile_)->WakeEventPage(extension_id,
                                                            base::DoNothing());
 }
+
+// TODO(crbug.com/413451043): move permission granting for component extensions
+// to ComponentExtensionContentSettingsAllowlist.
+void ComponentLoader::GrantPermissions(
+    const ExtensionId& extension_id,
+    std::initializer_list<ContentSettingsType> permissions) {
+  CHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  auto* component_extension_content_settings_allowlist =
+      ComponentExtensionContentSettingsAllowlist::Get(profile_);
+  const url::Origin host_origin = url::Origin::Create(GURL(base::StrCat(
+      {kExtensionScheme, url::kStandardSchemeSeparator, extension_id})));
+  component_extension_content_settings_allowlist
+      ->RegisterAutoGrantedPermissions(host_origin, std::move(permissions));
+}
+
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/component_loader.h b/chrome/browser/extensions/component_loader.h
index 59479a7..035de28 100644
--- a/chrome/browser/extensions/component_loader.h
+++ b/chrome/browser/extensions/component_loader.h
@@ -23,6 +23,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/common/buildflags.h"
+#include "components/content_settings/core/common/content_settings_types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "extensions/buildflags/buildflags.h"
 #include "extensions/common/extension_id.h"
@@ -128,9 +129,7 @@
       const base::FilePath::CharType* manifest_file_name,
       const base::FilePath::CharType* guest_manifest_file_name,
       base::OnceClosure done_cb);
-#endif  // BUILDFLAG(IS_CHROMEOS)
 
-#if BUILDFLAG(IS_CHROMEOS)
   // Add a component extension from a specific directory. Assumes that the
   // extension uses a different manifest file when this is a guest session
   // and that the manifest file lives in |root_directory|. Calls |done_cb|
@@ -149,7 +148,7 @@
                                         const std::string& description_string);
 
   void AddChromeOsSpeechSynthesisExtensions();
-#endif
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   void set_ignore_allowlist_for_testing(bool value) {
     ignore_allowlist_for_testing_ = value;
@@ -219,6 +218,13 @@
   void AddWebStoreApp();
 
 #if BUILDFLAG(IS_CHROMEOS)
+  void AddChromeApp();
+  void AddFileManagerExtension();
+  void AddGalleryExtension();
+  void AddImageLoaderExtension();
+  void AddGuestModeTestExtension(const base::FilePath& path);
+  void AddKeyboardApp();
+
   // Used as a reply callback by |AddComponentFromDir|.
   // Called with a |root_directory| and parsed |manifest| and invokes
   // |done_cb| after adding the extension.
@@ -229,15 +235,13 @@
       const std::optional<std::string>& description_string,
       base::OnceClosure done_cb,
       std::optional<base::Value::Dict> manifest);
-#endif  // BUILDFLAG(IS_CHROMEOS)
 
-#if BUILDFLAG(IS_CHROMEOS)
-  void AddChromeApp();
-  void AddFileManagerExtension();
-  void AddGalleryExtension();
-  void AddImageLoaderExtension();
-  void AddGuestModeTestExtension(const base::FilePath& path);
-  void AddKeyboardApp();
+  // Finishes loading an extension tts engine.
+  void FinishLoadSpeechSynthesisExtension(const ExtensionId& extension_id);
+
+  // Grant ContentSettingsType permissions to Extension.
+  void GrantPermissions(const ExtensionId& extension_id,
+                        std::initializer_list<ContentSettingsType> permissions);
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
   scoped_refptr<const Extension> CreateExtension(
@@ -246,9 +250,6 @@
   // Unloads |component| from the memory.
   void UnloadComponent(ComponentExtensionInfo* component);
 
-  // Finishes loading an extension tts engine.
-  void FinishLoadSpeechSynthesisExtension(const ExtensionId& extension_id);
-
   raw_ptr<Profile> profile_;
 
   raw_ptr<ExtensionSystem, AcrossTasksDanglingUntriaged> extension_system_;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 80995a1..b27cca2 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -9947,6 +9947,11 @@
     "expiry_milestone": -1
   },
   {
+    "name": "webrtc-wgc-require-border",
+    "owners": [ "eladalon@chromium.org", "kron@chromium.org" ],
+    "expiry_milestone": 180
+  },
+  {
     "name": "webtransport-developer-mode",
     "owners": [ "//net/quic/OWNERS" ],
     // This flag is used by developers to debug applications
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 3474fd0..afb4cb4 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -5758,6 +5758,11 @@
     "window area. The GDI API is used as window capture API when this flag is "
     "disabled.";
 
+const char kWebRtcWgcRequireBorderName[] = "Border around WGC captures";
+const char kWebRtcWgcRequireBorderDescription[] =
+    "When using WGC to capture a window or a screen, draw a border around the "
+    "captured surface.";
+
 const char kWindows11MicaTitlebarName[] = "Windows 11 Mica titlebar";
 const char kWindows11MicaTitlebarDescription[] =
     "Use the DWM system-drawn Mica titlebar on Windows 11, version 22H2 (build "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index ba31ee3..77e9517 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -3364,6 +3364,9 @@
 extern const char kWebRtcAllowWgcWindowCapturerName[];
 extern const char kWebRtcAllowWgcWindowCapturerDescription[];
 
+extern const char kWebRtcWgcRequireBorderName[];
+extern const char kWebRtcWgcRequireBorderDescription[];
+
 extern const char kWindows11MicaTitlebarName[];
 extern const char kWindows11MicaTitlebarDescription[];
 
diff --git a/chrome/browser/media/webrtc/capture_policy_utils.cc b/chrome/browser/media/webrtc/capture_policy_utils.cc
index 36797f37..debfad11 100644
--- a/chrome/browser/media/webrtc/capture_policy_utils.cc
+++ b/chrome/browser/media/webrtc/capture_policy_utils.cc
@@ -51,86 +51,12 @@
 #if BUILDFLAG(IS_CHROMEOS)
 crosapi::mojom::MultiCaptureService* g_multi_capture_service_for_testing =
     nullptr;
-
-void IsMultiCaptureAllowedForAnyOriginOnMainProfileResultReceived(
-    base::OnceCallback<void(bool)> callback,
-    content::BrowserContext* context,
-    bool is_multi_capture_allowed_for_any_origin_on_main_profile) {
-  // If the new MultiScreenCaptureAllowedForUrls policy permits access, exit
-  // early. If not, check the legacy
-  // GetDisplayMediaSetSelectAllScreensAllowedForUrls policy.
-  if (is_multi_capture_allowed_for_any_origin_on_main_profile) {
-    std::move(callback).Run(true);
-    return;
-  }
-
-  // TODO(b/329064666): Remove the checks below once the pivot to IWAs is
-  // complete.
-  Profile* profile = Profile::FromBrowserContext(context);
-  if (!profile) {
-    std::move(callback).Run(false);
-    return;
-  }
-
-  HostContentSettingsMap* host_content_settings_map =
-      HostContentSettingsMapFactory::GetForProfile(profile);
-  if (!host_content_settings_map) {
-    std::move(callback).Run(false);
-    return;
-  }
-  ContentSettingsForOneType content_settings =
-      host_content_settings_map->GetSettingsForOneType(
-          ContentSettingsType::ALL_SCREEN_CAPTURE);
-  std::move(callback).Run(std::ranges::any_of(
-      content_settings, [](const ContentSettingPatternSource& source) {
-        return source.GetContentSetting() ==
-               ContentSetting::CONTENT_SETTING_ALLOW;
-      }));
-}
-
-void CheckAllScreensMediaAllowedForIwaResultReceived(
-    base::OnceCallback<void(bool)> callback,
-    const GURL& url,
-    content::BrowserContext* context,
-    bool result) {
-  if (result) {
-    std::move(callback).Run(true);
-    return;
-  }
-
-  Profile* profile = Profile::FromBrowserContext(context);
-  if (!profile) {
-    std::move(callback).Run(false);
-    return;
-  }
-  HostContentSettingsMap* host_content_settings_map =
-      HostContentSettingsMapFactory::GetForProfile(profile);
-  if (!host_content_settings_map) {
-    std::move(callback).Run(false);
-    return;
-  }
-  ContentSetting auto_accept_enabled =
-      host_content_settings_map->GetContentSetting(
-          url, url, ContentSettingsType::ALL_SCREEN_CAPTURE);
-  std::move(callback).Run(auto_accept_enabled ==
-                          ContentSetting::CONTENT_SETTING_ALLOW);
-}
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace
 
 namespace capture_policy {
 
-// This pref connects to the GetDisplayMediaSetSelectAllScreensAllowedForUrls
-// policy. To avoid dynamic refresh, this pref will not be read directly, but
-// the value will be copied manually to the
-// kManagedAccessToGetAllScreensMediaInSessionAllowedForUrls pref, which is then
-// consumed by content settings to check if access to `getAllScreensMedia` shall
-// be permitted for a given origin.
-// TODO(b/329064666): Remove this pref once the pivot to IWAs is complete.
-const char kManagedAccessToGetAllScreensMediaAllowedForUrls[] =
-    "profile.managed_access_to_get_all_screens_media_allowed_for_urls";
-
 #if BUILDFLAG(IS_CHROMEOS)
 // This pref connects to the MultiScreenCaptureAllowedForUrls policy and will
 // replace the deprecated GetDisplayMediaSetSelectAllScreensAllowedForUrls
@@ -248,52 +174,35 @@
 }
 
 void RegisterProfilePrefs(PrefRegistrySimple* registry) {
-  registry->RegisterListPref(kManagedAccessToGetAllScreensMediaAllowedForUrls);
 #if BUILDFLAG(IS_CHROMEOS)
   registry->RegisterListPref(kManagedMultiScreenCaptureAllowedForUrls);
 #endif  // BUILDFLAG(IS_CHROMEOS)
 }
 
 void CheckGetAllScreensMediaAllowedForAnyOrigin(
-    content::BrowserContext* context,
     base::OnceCallback<void(bool)> callback) {
 #if BUILDFLAG(IS_CHROMEOS)
   if (crosapi::mojom::MultiCaptureService* multi_capture_service =
           GetMultiCaptureService()) {
     multi_capture_service->IsMultiCaptureAllowedForAnyOriginOnMainProfile(
-        base::BindOnce(
-            IsMultiCaptureAllowedForAnyOriginOnMainProfileResultReceived,
-            std::move(callback), context));
-  } else {
-    // If the multi capture service is not available with the required version,
-    // fall back to the original flow using the deprecated policy.
-    IsMultiCaptureAllowedForAnyOriginOnMainProfileResultReceived(
-        std::move(callback), context, /*result=*/false);
+        std::move(callback));
+    return;
   }
-#else
-  std::move(callback).Run(false);
 #endif  // BUILDFLAG(IS_CHROMEOS)
+  std::move(callback).Run(/*result=*/false);
 }
 
-void CheckGetAllScreensMediaAllowed(content::BrowserContext* context,
-                                    const GURL& url,
+void CheckGetAllScreensMediaAllowed(const GURL& url,
                                     base::OnceCallback<void(bool)> callback) {
 #if BUILDFLAG(IS_CHROMEOS)
   crosapi::mojom::MultiCaptureService* multi_capture_service =
       GetMultiCaptureService();
   if (multi_capture_service) {
-    multi_capture_service->IsMultiCaptureAllowed(
-        url, base::BindOnce(&CheckAllScreensMediaAllowedForIwaResultReceived,
-                            std::move(callback), std::move(url), context));
-  } else {
-    // If the multi capture service is not available with the required version,
-    // fall back to the original flow using the deprecated policy.
-    CheckAllScreensMediaAllowedForIwaResultReceived(
-        std::move(callback), std::move(url), context, /*result=*/false);
+    multi_capture_service->IsMultiCaptureAllowed(url, std::move(callback));
+    return;
   }
-#else
-  std::move(callback).Run(false);
 #endif  // BUILDFLAG(IS_CHROMEOS)
+  std::move(callback).Run(/*result=*/false);
 }
 
 #if BUILDFLAG(ENABLE_SCREEN_CAPTURE)
diff --git a/chrome/browser/media/webrtc/capture_policy_utils.h b/chrome/browser/media/webrtc/capture_policy_utils.h
index 1887182..d231240 100644
--- a/chrome/browser/media/webrtc/capture_policy_utils.h
+++ b/chrome/browser/media/webrtc/capture_policy_utils.h
@@ -15,7 +15,6 @@
 class PrefService;
 
 namespace content {
-class BrowserContext;
 class WebContents;
 }  // namespace content
 
@@ -38,8 +37,6 @@
 
 namespace capture_policy {
 
-extern const char kManagedAccessToGetAllScreensMediaAllowedForUrls[];
-
 #if BUILDFLAG(IS_CHROMEOS)
 extern const char kManagedMultiScreenCaptureAllowedForUrls[];
 
@@ -82,12 +79,10 @@
 void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
 // TODO(crbug.com/40230867): Use Origin instead of GURL.
-void CheckGetAllScreensMediaAllowed(content::BrowserContext* context,
-                                    const GURL& url,
+void CheckGetAllScreensMediaAllowed(const GURL& url,
                                     base::OnceCallback<void(bool)> callback);
 
 void CheckGetAllScreensMediaAllowedForAnyOrigin(
-    content::BrowserContext* context,
     base::OnceCallback<void(bool)> callback);
 
 #if BUILDFLAG(ENABLE_SCREEN_CAPTURE)
diff --git a/chrome/browser/media/webrtc/capture_policy_utils_browsertest.cc b/chrome/browser/media/webrtc/capture_policy_utils_browsertest.cc
index 0aa0cb5..cb0698d 100644
--- a/chrome/browser/media/webrtc/capture_policy_utils_browsertest.cc
+++ b/chrome/browser/media/webrtc/capture_policy_utils_browsertest.cc
@@ -114,75 +114,15 @@
 };
 
 IN_PROC_BROWSER_TEST_P(SelectAllScreensTest, SelectAllScreensTestOrigins) {
-  Browser* current_browser = browser();
-  TabStripModel* current_tab_strip_model = current_browser->tab_strip_model();
-  content::WebContents* current_web_contents =
-      current_tab_strip_model->GetWebContentsAt(0);
-
   base::test::TestFuture<bool> future;
-  capture_policy::CheckGetAllScreensMediaAllowed(
-      current_web_contents->GetBrowserContext(), GURL(GetParam().testing_url),
-      future.GetCallback());
+  capture_policy::CheckGetAllScreensMediaAllowed(GURL(GetParam().testing_url),
+                                                 future.GetCallback());
   ASSERT_TRUE(future.Wait());
   EXPECT_EQ(GetParam().expected_is_get_all_screens_media_allowed,
             future.Get<bool>());
 }
 
 INSTANTIATE_TEST_SUITE_P(
-    DeprecatedPolicySelectAllScreensTestWithParams,
-    SelectAllScreensTest,
-    testing::Values(
-        TestParam({
-            .allow_list_policy_name =
-                policy::key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-            .allow_listed_origins = {""},
-            .testing_url = "",
-            .expected_is_get_all_screens_media_allowed = false,
-        }),
-        TestParam({
-            .allow_list_policy_name =
-                policy::key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-            .allow_listed_origins = {},
-            .testing_url = "https://www.chromium.org",
-            .expected_is_get_all_screens_media_allowed = false,
-        }),
-        TestParam({
-            .allow_list_policy_name =
-                policy::key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-            .allow_listed_origins = {},
-            .testing_url = "",
-            .expected_is_get_all_screens_media_allowed = false,
-        }),
-        TestParam({
-            .allow_list_policy_name =
-                policy::key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-            .allow_listed_origins = {"https://www.chromium.org"},
-            .testing_url = "https://www.chromium.org",
-            .expected_is_get_all_screens_media_allowed = true,
-        }),
-        TestParam({
-            .allow_list_policy_name =
-                policy::key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-            .allow_listed_origins = {"[*.]chromium.org"},
-            .testing_url = "https://sub.chromium.org",
-            .expected_is_get_all_screens_media_allowed = true,
-        }),
-        TestParam({
-            .allow_list_policy_name =
-                policy::key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-            .allow_listed_origins = {"[*.]chrome.org", "[*.]chromium.com"},
-            .testing_url = "https://www.chromium.org",
-            .expected_is_get_all_screens_media_allowed = false,
-        }),
-        TestParam({
-            .allow_list_policy_name =
-                policy::key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-            .allow_listed_origins = {"[*.]chrome.org", "[*.]chromium.org"},
-            .testing_url = "https://www.chromium.org",
-            .expected_is_get_all_screens_media_allowed = true,
-        })));
-
-INSTANTIATE_TEST_SUITE_P(
     SelectAllScreensTestWithParams,
     SelectAllScreensTest,
     testing::Values(TestParam({
@@ -253,7 +193,6 @@
          GetParam().expected_allowed_origins) {
       base::test::TestFuture<bool> future;
       capture_policy::CheckGetAllScreensMediaAllowed(
-          current_web_contents->GetBrowserContext(),
           GURL(expected_allowed_origin), future.GetCallback());
       ASSERT_TRUE(future.Wait());
       EXPECT_TRUE(future.Get<bool>());
@@ -263,7 +202,6 @@
          GetParam().expected_forbidden_origins) {
       base::test::TestFuture<bool> future;
       capture_policy::CheckGetAllScreensMediaAllowed(
-          current_web_contents->GetBrowserContext(),
           GURL(expected_forbidden_origin), future.GetCallback());
       ASSERT_TRUE(future.Wait());
       EXPECT_FALSE(future.Get<bool>());
@@ -284,27 +222,6 @@
 }
 
 INSTANTIATE_TEST_SUITE_P(
-    DeprecatedPolicySelectAllScreensDynamicRefreshTestWithParams,
-    SelectAllScreensDynamicRefreshTest,
-    testing::Values(
-        NoRefreshTestParam({
-            .allow_list_policy_name =
-                policy::key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-            .original_allowlisted_origins = {"https://www.chromium.org"},
-            .updated_allowlisted_origins = {},
-            .expected_allowed_origins = {"https://www.chromium.org"},
-            .expected_forbidden_origins = {"https://www.chromium.com"},
-        }),
-        NoRefreshTestParam({
-            .allow_list_policy_name =
-                policy::key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-            .original_allowlisted_origins = {},
-            .updated_allowlisted_origins = {"https://www.chromium.org"},
-            .expected_allowed_origins = {},
-            .expected_forbidden_origins = {},
-        })));
-
-INSTANTIATE_TEST_SUITE_P(
     SelectAllScreensDynamicRefreshTestWithParams,
     SelectAllScreensDynamicRefreshTest,
     testing::Values(NoRefreshTestParam({
@@ -326,6 +243,81 @@
                         .expected_forbidden_origins = {kValidIsolatedAppId1,
                                                        kValidIsolatedAppId2},
                     })));
+
+class MultiCaptureTest
+    : public InProcessBrowserTest,
+      public ::testing::WithParamInterface<
+          std::tuple<std::vector<std::string>, std::string>> {
+ public:
+  void SetUpInProcessBrowserTestFixture() override {
+    InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
+    provider_.SetDefaultReturns(/*is_initialization_complete_return=*/true,
+                                /*is_first_policy_load_complete_return=*/true);
+    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
+    policy_map_.Set(policy::key::kMultiScreenCaptureAllowedForUrls,
+                    policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
+                    policy::POLICY_SOURCE_CLOUD,
+                    base::Value(AllowedOriginsList()),
+                    /*external_data_fetcher=*/nullptr);
+    provider_.UpdateChromePolicy(policy_map_);
+  }
+
+  const std::vector<std::string>& AllowedOrigins() const {
+    return std::get<0>(GetParam());
+  }
+
+  base::Value::List AllowedOriginsList() const {
+    base::Value::List allowed_origins;
+    for (const auto& origin : AllowedOrigins()) {
+      allowed_origins.Append(base::Value(origin));
+    }
+    return allowed_origins;
+  }
+
+  const std::string& CurrentOrigin() const { return std::get<1>(GetParam()); }
+
+ protected:
+  bool ExpectedIsMultiCaptureAllowed() {
+    std::vector<std::string> allowed_urls = AllowedOrigins();
+    return base::Contains(allowed_urls, CurrentOrigin());
+  }
+
+  bool ExpectedIsMultiCaptureAllowedForAnyUrl() {
+    return !AllowedOrigins().empty();
+  }
+
+ private:
+  policy::PolicyMap policy_map_;
+  policy::MockConfigurationPolicyProvider provider_;
+};
+
+IN_PROC_BROWSER_TEST_P(MultiCaptureTest, IsMultiCaptureAllowedBasedOnPolicy) {
+  base::test::TestFuture<bool> future;
+  capture_policy::CheckGetAllScreensMediaAllowed(GURL(CurrentOrigin()),
+                                                 future.GetCallback());
+  ASSERT_TRUE(future.Wait());
+  EXPECT_EQ(ExpectedIsMultiCaptureAllowed(), future.Get<bool>());
+}
+
+IN_PROC_BROWSER_TEST_P(MultiCaptureTest, IsMultiCaptureAllowedForAnyUrl) {
+  base::test::TestFuture<bool> future;
+  capture_policy::CheckGetAllScreensMediaAllowedForAnyOrigin(
+      future.GetCallback());
+  ASSERT_TRUE(future.Wait());
+  EXPECT_EQ(ExpectedIsMultiCaptureAllowedForAnyUrl(), future.Get<bool>());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    MultiCaptureTestCases,
+    MultiCaptureTest,
+    ::testing::Combine(
+        // Allowed origins
+        ::testing::ValuesIn({std::vector<std::string>{},
+                             std::vector<std::string>{kValidIsolatedAppId1}}),
+        // Origin to test
+        ::testing::ValuesIn({std::string(kValidIsolatedAppId1),
+                             std::string(kValidIsolatedAppId2)})));
+
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 using CaptureUtilsBrowserTest = InProcessBrowserTest;
@@ -336,15 +328,9 @@
 
 IN_PROC_BROWSER_TEST_F(CaptureUtilsBrowserTest,
                        NoPolicySetMultiCaptureServiceMaybeExists) {
-  Browser* current_browser = browser();
-  TabStripModel* current_tab_strip_model = current_browser->tab_strip_model();
-  content::WebContents* current_web_contents =
-      current_tab_strip_model->GetWebContentsAt(0);
-
   base::test::TestFuture<bool> future;
-  capture_policy::CheckGetAllScreensMediaAllowed(
-      current_web_contents->GetBrowserContext(), GURL(kValidIsolatedAppId1),
-      future.GetCallback());
+  capture_policy::CheckGetAllScreensMediaAllowed(GURL(kValidIsolatedAppId1),
+                                                 future.GetCallback());
   ASSERT_TRUE(future.Wait());
   EXPECT_FALSE(future.Get<bool>());
 }
diff --git a/chrome/browser/media/webrtc/capture_policy_utils_unittest.cc b/chrome/browser/media/webrtc/capture_policy_utils_unittest.cc
index e453c94..437948ad 100644
--- a/chrome/browser/media/webrtc/capture_policy_utils_unittest.cc
+++ b/chrome/browser/media/webrtc/capture_policy_utils_unittest.cc
@@ -33,10 +33,6 @@
 constexpr char kTestSite1[] = "https://foo.test.org";
 constexpr char kTestSite1Pattern[] = "foo.test.org";
 constexpr char kTestSite1NonMatchingPattern[] = "foo.org";
-
-#if BUILDFLAG(IS_CHROMEOS)
-constexpr char kAccountId[] = "test_1@example.com";
-#endif  // BUILDFLAG(IS_CHROMEOS)
 }  // namespace
 
 class CapturePolicyUtilsTest : public testing::Test {
@@ -276,101 +272,3 @@
 
   EXPECT_EQ(expected_media_types, actual_media_types);
 }
-
-#if BUILDFLAG(IS_CHROMEOS)
-
-class MultiCaptureTest
-    : public testing::Test,
-      public ::testing::WithParamInterface<
-          std::tuple<bool, std::vector<std::string>, std::string>> {
- public:
-  void SetUp() override {
-    testing::Test::SetUp();
-
-    fake_user_manager_.Reset(std::make_unique<ash::FakeChromeUserManager>());
-    CHECK(profile_manager_.SetUp());
-    profile_ = profile_manager_.CreateTestingProfile(kAccountId);
-
-    AccountId account_id = AccountId::FromUserEmail(kAccountId);
-    fake_user_manager_->AddUserWithAffiliationAndTypeAndProfile(
-        account_id, /*is_affiliated=*/true, user_manager::UserType::kRegular,
-        profile_);
-    fake_user_manager_->LoginUser(account_id);
-
-    // Settings required to create startup data.
-    if (!ash::LoginState::IsInitialized()) {
-      ash::LoginState::Initialize();
-    }
-    cros_api_manager_ = std::make_unique<crosapi::CrosapiManager>();
-
-    HostContentSettingsMap* content_settings =
-        HostContentSettingsMapFactory::GetForProfile(profile());
-    for (const std::string& url : AllowedOrigins()) {
-      content_settings->SetContentSettingDefaultScope(
-          GURL(url), GURL(url), ContentSettingsType::ALL_SCREEN_CAPTURE,
-          ContentSetting::CONTENT_SETTING_ALLOW);
-    }
-  }
-
-  void TearDown() override {
-    profile_ = nullptr;
-    // ash::LoginState::Shutdown();
-  }
-
-  Profile* profile() { return profile_; }
-  bool IsMainProfile() const { return std::get<0>(GetParam()); }
-  std::vector<std::string> AllowedOrigins() const {
-    return std::get<1>(GetParam());
-  }
-  std::string CurrentOrigin() const { return std::get<2>(GetParam()); }
-
- protected:
-  bool ExpectedIsMultiCaptureAllowed() {
-    std::vector<std::string> allowed_urls = AllowedOrigins();
-    return base::Contains(allowed_urls, CurrentOrigin());
-  }
-
-  bool ExpectedIsMultiCaptureAllowedForAnyUrl() {
-    return !AllowedOrigins().empty();
-  }
-
- private:
-  raw_ptr<TestingProfile> profile_;
-  content::BrowserTaskEnvironment task_environment_;
-  std::unique_ptr<crosapi::CrosapiManager> cros_api_manager_;
-  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
-      fake_user_manager_;
-  TestingProfileManager profile_manager_{TestingBrowserProcess::GetGlobal()};
-};
-
-TEST_P(MultiCaptureTest, IsMultiCaptureAllowedBasedOnPolicy) {
-  base::test::TestFuture<bool> future;
-  capture_policy::CheckGetAllScreensMediaAllowed(
-      profile(), GURL(CurrentOrigin()), future.GetCallback());
-  ASSERT_TRUE(future.Wait());
-  EXPECT_EQ(ExpectedIsMultiCaptureAllowed(), future.Get<bool>());
-}
-
-TEST_P(MultiCaptureTest, IsMultiCaptureAllowedForAnyUrl) {
-  base::test::TestFuture<bool> future;
-  capture_policy::CheckGetAllScreensMediaAllowedForAnyOrigin(
-      profile(), future.GetCallback());
-  ASSERT_TRUE(future.Wait());
-  EXPECT_EQ(ExpectedIsMultiCaptureAllowedForAnyUrl(), future.Get<bool>());
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    MultiCaptureTestCases,
-    MultiCaptureTest,
-    ::testing::Combine(
-        // Is main profile?
-        ::testing::Bool(),
-        // Allowed origins
-        ::testing::ValuesIn({std::vector<std::string>{},
-                             std::vector<std::string>{
-                                 "https://www.google.com"}}),
-        // Origin to test
-        ::testing::ValuesIn({std::string("https://www.google.com"),
-                             std::string("https://www.notallowed.com")})));
-
-#endif  // BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/password_manager/password_change/change_form_submission_verifier.cc b/chrome/browser/password_manager/password_change/change_form_submission_verifier.cc
index e531345e..31efaafe 100644
--- a/chrome/browser/password_manager/password_change/change_form_submission_verifier.cc
+++ b/chrome/browser/password_manager/password_change/change_form_submission_verifier.cc
@@ -4,17 +4,22 @@
 
 #include "chrome/browser/password_manager/password_change/change_form_submission_verifier.h"
 
+#include "base/functional/concurrent_closures.h"
 #include "base/metrics/histogram_functions.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
+#include "chrome/browser/page_content_annotations/page_content_extraction_service.h"
+#include "chrome/browser/page_content_annotations/page_content_extraction_service_factory.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/optimization_guide/content/browser/page_content_proto_provider.h"
 #include "components/optimization_guide/core/model_quality/model_execution_logging_wrappers.h"
 #include "components/optimization_guide/core/model_quality/model_quality_log_entry.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/core/optimization_guide_model_executor.h"
 #include "components/optimization_guide/core/optimization_guide_proto_util.h"
 #include "components/optimization_guide/proto/model_execution.pb.h"
+#include "components/page_content_annotations/core/page_content_annotations_features.h"
 #include "components/password_manager/content/browser/content_password_manager_driver.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/web_contents.h"
@@ -33,6 +38,7 @@
 using FinalModelStatus = optimization_guide::proto::FinalModelStatus;
 using QualityLogEntry =
     std::unique_ptr<optimization_guide::ModelQualityLogEntry>;
+using page_content_annotations::PageContentExtractionService;
 
 // Max numbers of nodes for the AX Tree Update Snapshot.
 constexpr int kMaxNodesInAXTreeSnapshot = 5000;
@@ -127,8 +133,22 @@
     content::WebContents* web_contents,
     FormSubmissionResultCallback callback)
     : web_contents_(web_contents->GetWeakPtr()),
+      capture_annotated_page_content_(
+          base::BindOnce(&optimization_guide::GetAIPageContent,
+                         web_contents,
+                         optimization_guide::DefaultAIPageContentOptions())),
       callback_(std::move(callback)) {}
 
+ChangeFormSubmissionVerifier::ChangeFormSubmissionVerifier(
+    base::PassKey<class ChangeFormSubmissionVerifierTest>,
+    content::WebContents* web_contents,
+    base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>
+        capture_annotated_page_content,
+    FormSubmissionResultCallback callback)
+    : ChangeFormSubmissionVerifier(web_contents, std::move(callback)) {
+  capture_annotated_page_content_ = std::move(capture_annotated_page_content);
+}
+
 ChangeFormSubmissionVerifier::~ChangeFormSubmissionVerifier() = default;
 
 void ChangeFormSubmissionVerifier::FillChangePasswordForm(
@@ -154,7 +174,7 @@
   // captured.
   timeout_timer_.Start(FROM_HERE,
                        ChangeFormSubmissionVerifier::kSubmissionWaitingTimeout,
-                       this, &ChangeFormSubmissionVerifier::RequestAXTree);
+                       this, &ChangeFormSubmissionVerifier::RequestPageContent);
 }
 
 void ChangeFormSubmissionVerifier::OnPasswordFormSubmission(
@@ -243,7 +263,7 @@
   // looking for a submit button.
 }
 
-void ChangeFormSubmissionVerifier::RequestAXTree() {
+void ChangeFormSubmissionVerifier::RequestPageContent() {
   // If browser didn't receive confirmation about change password form
   // submission from driver, fail immediately.
   if (!password_form_submitted_ || !web_contents_) {
@@ -253,23 +273,53 @@
 
   base::UmaHistogramBoolean(kPasswordChangeSubmittedHistogram,
                             submission_detected_);
+
+  base::ConcurrentClosures concurrent_closures;
+  std::move(capture_annotated_page_content_)
+      .Run(base::BindOnce(
+               &ChangeFormSubmissionVerifier::OnAnnotatedPageContentReceived,
+               weak_ptr_factory_.GetWeakPtr())
+               .Then(concurrent_closures.CreateClosure()));
+  // TODO(crbug.com/409946698): Delete this when removing support for AX tree
+  // prompts.
   web_contents_->RequestAXTreeSnapshot(
-      base::BindOnce(&ChangeFormSubmissionVerifier::ProcessTree,
-                     weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&ChangeFormSubmissionVerifier::OnAxTreeReceived,
+                     weak_ptr_factory_.GetWeakPtr())
+          .Then(concurrent_closures.CreateClosure()),
       ui::AXMode::kWebContents, kMaxNodesInAXTreeSnapshot,
       /* timeout= */ {}, content::WebContents::AXTreeSnapshotPolicy::kAll);
+  std::move(concurrent_closures)
+      .Done(base::BindOnce(
+          &ChangeFormSubmissionVerifier::CheckSubmissionSuccessful,
+          weak_ptr_factory_.GetWeakPtr()));
 }
 
-void ChangeFormSubmissionVerifier::ProcessTree(
+void ChangeFormSubmissionVerifier::OnAnnotatedPageContentReceived(
+    std::optional<optimization_guide::AIPageContentResult> page_content) {
+  if (page_content.has_value()) {
+    *check_submission_successful_request_.mutable_page_context()
+         ->mutable_annotated_page_content() = std::move(page_content->proto);
+  }
+}
+
+void ChangeFormSubmissionVerifier::OnAxTreeReceived(
     ui::AXTreeUpdate& ax_tree_update) {
   ProtoTreeUpdate ax_tree_proto;
   optimization_guide::PopulateAXTreeUpdateProto(ax_tree_update, &ax_tree_proto);
   // Construct request.
-  optimization_guide::proto::PasswordChangeRequest request;
-  optimization_guide::proto::PageContext* page_context =
-      request.mutable_page_context();
-  *page_context->mutable_ax_tree_data() = std::move(ax_tree_proto);
+  *check_submission_successful_request_.mutable_page_context()
+       ->mutable_ax_tree_data() = std::move(ax_tree_proto);
+}
 
+void ChangeFormSubmissionVerifier::CheckSubmissionSuccessful() {
+  if (!check_submission_successful_request_.has_page_context() ||
+      !check_submission_successful_request_.page_context()
+           .has_annotated_page_content()) {
+    // TODO (crbug.com/413318086): Add metrics to handle failure of capturing
+    // annotated page content.
+    std::move(callback_).Run(false);
+    return;
+  }
   optimization_guide::ModelExecutionCallbackWithLogging<
       optimization_guide::proto::PasswordChangeSubmissionLoggingData>
       wrapper_callback = password_manager::metrics_util::TimeCallback(
@@ -281,12 +331,12 @@
   optimization_guide::ExecuteModelWithLogging(
       GetOptimizationService(),
       optimization_guide::ModelBasedCapabilityKey::kPasswordChangeSubmission,
-      request,
+      check_submission_successful_request_,
       /*execution_timeout=*/std::nullopt, std::move(wrapper_callback));
 }
 
 OptimizationGuideKeyedService*
-ChangeFormSubmissionVerifier::GetOptimizationService() {
+ChangeFormSubmissionVerifier::GetOptimizationService() const {
   return OptimizationGuideKeyedServiceFactory::GetForProfile(
       Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
 }
diff --git a/chrome/browser/password_manager/password_change/change_form_submission_verifier.h b/chrome/browser/password_manager/password_change/change_form_submission_verifier.h
index aad0b299..dfeee84f 100644
--- a/chrome/browser/password_manager/password_change/change_form_submission_verifier.h
+++ b/chrome/browser/password_manager/password_change/change_form_submission_verifier.h
@@ -11,6 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
 #include "components/autofill/core/common/form_data.h"
+#include "components/optimization_guide/content/browser/page_content_proto_provider.h"
 #include "components/optimization_guide/core/optimization_guide_model_executor.h"
 #include "components/password_manager/core/browser/password_form.h"
 #include "ui/accessibility/ax_tree_update.h"
@@ -63,6 +64,12 @@
 
   ChangeFormSubmissionVerifier(content::WebContents* web_contents,
                                FormSubmissionResultCallback callback);
+  ChangeFormSubmissionVerifier(
+      base::PassKey<class ChangeFormSubmissionVerifierTest>,
+      content::WebContents* web_contents,
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>
+          capture_annotated_page_content,
+      FormSubmissionResultCallback callback);
   ~ChangeFormSubmissionVerifier();
 
   // Starts chain of actions:
@@ -100,9 +107,12 @@
       base::WeakPtr<password_manager::PasswordManagerDriver> driver,
       bool success);
 
-  void RequestAXTree();
+  void RequestPageContent();
 
-  void ProcessTree(ui::AXTreeUpdate& ax_tree_update);
+  void OnAnnotatedPageContentReceived(
+      std::optional<optimization_guide::AIPageContentResult> page_content);
+  void OnAxTreeReceived(ui::AXTreeUpdate& ax_tree_update);
+  void CheckSubmissionSuccessful();
   void OnExecutionResponseCallback(
       optimization_guide::OptimizationGuideModelExecutionResult
           execution_result,
@@ -110,12 +120,18 @@
           optimization_guide::proto::PasswordChangeSubmissionLoggingData>
           logging_data);
 
-  OptimizationGuideKeyedService* GetOptimizationService();
+  OptimizationGuideKeyedService* GetOptimizationService() const;
 
   base::OneShotTimer timeout_timer_;
   base::WeakPtr<content::WebContents> web_contents_;
+  base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>
+      capture_annotated_page_content_;
   FormSubmissionResultCallback callback_;
   std::unique_ptr<password_manager::PasswordFormManager> form_manager_;
+  // TODO(crbug.com/409946698): Delete this when removing support for AX tree
+  // prompts.
+  optimization_guide::proto::PasswordChangeRequest
+      check_submission_successful_request_;
 
   bool submission_detected_ = false;
   bool password_form_submitted_ = false;
diff --git a/chrome/browser/password_manager/password_change/change_form_submission_verifier_unittest.cc b/chrome/browser/password_manager/password_change/change_form_submission_verifier_unittest.cc
index 591c643..e8f4d5c 100644
--- a/chrome/browser/password_manager/password_change/change_form_submission_verifier_unittest.cc
+++ b/chrome/browser/password_manager/password_change/change_form_submission_verifier_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/test/gmock_callback_support.h"
 #include "base/test/gmock_move_support.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
 #include "base/test/run_until.h"
 #include "base/test/test_future.h"
 #include "chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.h"
@@ -184,9 +185,12 @@
 
   std::unique_ptr<ChangeFormSubmissionVerifier> CreateVerifier(
       password_manager::PasswordFormManager* manager,
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>
+          capture_annotated_page_content,
       base::OnceCallback<void(bool)> result_callback) {
     auto verifier = std::make_unique<ChangeFormSubmissionVerifier>(
-        web_contents(), std::move(result_callback));
+        base::PassKey<class ChangeFormSubmissionVerifierTest>(), web_contents(),
+        std::move(capture_annotated_page_content), std::move(result_callback));
     verifier->FillChangePasswordForm(manager, kOldPassword, kNewPassword);
     return verifier;
   }
@@ -215,8 +219,15 @@
   auto form_manager = CreateFormManager();
 
   base::test::TestFuture<bool> completion_future;
+  base::MockCallback<
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>>
+      capture_annotated_page_content;
+  EXPECT_CALL(capture_annotated_page_content, Run)
+      .WillOnce(base::test::RunOnceCallback<0>(
+          optimization_guide::AIPageContentResult()));
   auto verifier =
-      CreateVerifier(form_manager.get(), completion_future.GetCallback());
+      CreateVerifier(form_manager.get(), capture_annotated_page_content.Get(),
+                     completion_future.GetCallback());
 
   base::RunLoop run_loop;
   EXPECT_CALL(driver(), FillChangePasswordForm)
@@ -242,8 +253,15 @@
   auto form_manager = CreateFormManager();
 
   base::test::TestFuture<bool> completion_future;
+  base::MockCallback<
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>>
+      capture_annotated_page_content;
+  EXPECT_CALL(capture_annotated_page_content, Run)
+      .WillOnce(base::test::RunOnceCallback<0>(
+          optimization_guide::AIPageContentResult()));
   auto verifier =
-      CreateVerifier(form_manager.get(), completion_future.GetCallback());
+      CreateVerifier(form_manager.get(), capture_annotated_page_content.Get(),
+                     completion_future.GetCallback());
 
   base::RunLoop run_loop;
   EXPECT_CALL(driver(), FillChangePasswordForm)
@@ -265,8 +283,15 @@
   auto form_manager = CreateFormManager();
 
   base::test::TestFuture<bool> completion_future;
+  base::MockCallback<
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>>
+      capture_annotated_page_content;
+  EXPECT_CALL(capture_annotated_page_content, Run)
+      .WillOnce(base::test::RunOnceCallback<0>(
+          optimization_guide::AIPageContentResult()));
   auto verifier =
-      CreateVerifier(form_manager.get(), completion_future.GetCallback());
+      CreateVerifier(form_manager.get(), capture_annotated_page_content.Get(),
+                     completion_future.GetCallback());
 
   base::RunLoop run_loop;
   EXPECT_CALL(driver(), FillChangePasswordForm)
@@ -297,8 +322,13 @@
   auto form_manager = CreateFormManager();
 
   base::test::TestFuture<bool> completion_future;
+  base::MockCallback<
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>>
+      capture_annotated_page_content;
+  EXPECT_CALL(capture_annotated_page_content, Run).Times(0);
   auto verifier =
-      CreateVerifier(form_manager.get(), completion_future.GetCallback());
+      CreateVerifier(form_manager.get(), capture_annotated_page_content.Get(),
+                     completion_future.GetCallback());
 
   // Expect a call to FillChangePasswordForm, although don't invoke completion
   // callback.
@@ -316,8 +346,15 @@
   auto form_manager = CreateFormManager();
 
   base::test::TestFuture<bool> completion_future;
+  base::MockCallback<
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>>
+      capture_annotated_page_content;
+  EXPECT_CALL(capture_annotated_page_content, Run)
+      .WillOnce(base::test::RunOnceCallback<0>(
+          optimization_guide::AIPageContentResult()));
   auto verifier =
-      CreateVerifier(form_manager.get(), completion_future.GetCallback());
+      CreateVerifier(form_manager.get(), capture_annotated_page_content.Get(),
+                     completion_future.GetCallback());
 
   base::RunLoop run_loop;
   base::OnceCallback<void(const std::optional<autofill::FormData>&)> callback;
@@ -346,8 +383,15 @@
   auto form_manager = CreateFormManager();
 
   base::test::TestFuture<bool> completion_future;
+  base::MockCallback<
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>>
+      capture_annotated_page_content;
+  EXPECT_CALL(capture_annotated_page_content, Run)
+      .WillOnce(base::test::RunOnceCallback<0>(
+          optimization_guide::AIPageContentResult()));
   auto verifier =
-      CreateVerifier(form_manager.get(), completion_future.GetCallback());
+      CreateVerifier(form_manager.get(), capture_annotated_page_content.Get(),
+                     completion_future.GetCallback());
 
   base::RunLoop run_loop;
   EXPECT_CALL(driver(), FillChangePasswordForm)
@@ -367,3 +411,28 @@
 
   EXPECT_TRUE(completion_future.Get());
 }
+
+TEST_F(ChangeFormSubmissionVerifierTest, FailsCapturingAnnoatedPageContent) {
+  auto form_manager = CreateFormManager();
+
+  base::test::TestFuture<bool> completion_future;
+  base::MockCallback<
+      base::OnceCallback<void(optimization_guide::OnAIPageContentDone)>>
+      capture_annotated_page_content;
+  EXPECT_CALL(capture_annotated_page_content, Run)
+      .WillOnce(base::test::RunOnceCallback<0>(std::nullopt));
+  auto verifier =
+      CreateVerifier(form_manager.get(), capture_annotated_page_content.Get(),
+                     completion_future.GetCallback());
+
+  base::RunLoop run_loop;
+  EXPECT_CALL(driver(), FillChangePasswordForm)
+      .WillOnce(RunOnceCallback<5>(CreateTestPasswordFormData()));
+  EXPECT_CALL(driver(), SubmitFormWithEnter)
+      .WillOnce(DoAll(Invoke(&run_loop, &base::RunLoop::Quit),
+                      RunOnceCallback<1>(/*success=*/true)));
+  run_loop.Run();
+
+  EXPECT_CALL(*optimization_service(), ExecuteModel).Times(0);
+  EXPECT_FALSE(completion_future.Get());
+}
diff --git a/chrome/browser/password_manager/password_change_browsertest.cc b/chrome/browser/password_manager/password_change_browsertest.cc
index b758e9d..36da51dd 100644
--- a/chrome/browser/password_manager/password_change_browsertest.cc
+++ b/chrome/browser/password_manager/password_change_browsertest.cc
@@ -63,6 +63,7 @@
 using ::testing::_;
 using ::testing::An;
 using ::testing::Contains;
+using ::testing::DoAll;
 using ::testing::Invoke;
 using ::testing::NiceMock;
 using ::testing::Return;
@@ -205,18 +206,29 @@
                 ExecuteModel(optimization_guide::ModelBasedCapabilityKey::
                                  kPasswordChangeSubmission,
                              _, _, _))
-        .WillOnce(WithArg<3>(Invoke([response,
-                                     logs_uploader_weak_ptr](auto callback) {
-          base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-              FROM_HERE,
-              base::BindOnce(
-                  std::move(callback),
-                  optimization_guide::OptimizationGuideModelExecutionResult(
-                      optimization_guide::AnyWrapProto(response),
-                      /*execution_info=*/nullptr),
-                  std::make_unique<optimization_guide::ModelQualityLogEntry>(
-                      logs_uploader_weak_ptr)));
-        })));
+        .WillOnce(DoAll(
+            WithArg<1>([&](const google::protobuf::MessageLite& request) {
+              auto& password_change_request = static_cast<
+                  const optimization_guide::proto::PasswordChangeRequest&>(
+                  request);
+              ASSERT_TRUE(password_change_request.page_context()
+                              .has_annotated_page_content());
+              ASSERT_TRUE(
+                  password_change_request.page_context().has_ax_tree_data());
+            }),
+            WithArg<3>(Invoke([response,
+                               logs_uploader_weak_ptr](auto callback) {
+              base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+                  FROM_HERE,
+                  base::BindOnce(
+                      std::move(callback),
+                      optimization_guide::OptimizationGuideModelExecutionResult(
+                          optimization_guide::AnyWrapProto(response),
+                          /*execution_info=*/nullptr),
+                      std::make_unique<
+                          optimization_guide::ModelQualityLogEntry>(
+                          logs_uploader_weak_ptr)));
+            }))));
   }
 
   void CheckPasswordsSavedOnFailure(const std::string& username,
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 9e4a3e0..14d207b 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -2161,10 +2161,6 @@
   { key::kKioskActiveWiFiCredentialsScopeChangeEnabled,
     prefs::kKioskActiveWiFiCredentialsScopeChangeEnabled,
     base::Value::Type::BOOLEAN },
-  // TODO(b/329064666): Remove this pref once the pivot to IWAs is complete.
-  { key::kGetDisplayMediaSetSelectAllScreensAllowedForUrls,
-    capture_policy::kManagedAccessToGetAllScreensMediaAllowedForUrls,
-    base::Value::Type::LIST },
   { key::kKioskChromeAppsForceAllowed,
     prefs::kKioskChromeAppsForceAllowed,
     base::Value::Type::BOOLEAN },
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 535e989..259168ef 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -561,23 +561,7 @@
     "esim.last_upload_euicc_status";
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
-// Deprecated 04/2024.
-inline constexpr char kOmniboxInstantKeywordUsed[] =
-    "omnibox.instant_keyword_used";
-
-// Deprecated 04/2024.
-inline constexpr char kWebAppPreinstalledAppWindowExperiment[] =
-    "web_apps.preinstalled_app_window_experiment";
-
-// Deprecated 04/2024.
-inline constexpr char kDIPSTimerLastUpdate[] = "dips_timer_last_update";
-
 #if BUILDFLAG(IS_CHROMEOS)
-// Deprecated 04/2024
-constexpr char kMetricsUserInheritOwnerConsent[] =
-    "metrics.user_inherit_owner_consent";
-constexpr char kGlanceablesEnabled[] = "ash.glanceables_enabled";
-
 // Deprecated 05/2024.
 // A preference to keep track of the device registered time.
 constexpr char kDeviceRegisteredTime[] = "DeviceRegisteredTime";
@@ -1102,6 +1086,10 @@
     "bookmarks.added_since_power_bookmarks_launch";
 inline constexpr char kGlicRolloutEligibility[] = "glic.rollout_eligibility";
 
+// Deprecated 04/2025.
+inline constexpr char kManagedAccessToGetAllScreensMediaAllowedForUrls[] =
+    "profile.managed_access_to_get_all_screens_media_allowed_for_urls";
+
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
@@ -1219,20 +1207,7 @@
     user_prefs::PrefRegistrySyncable* registry) {
   chrome_browser_net::secure_dns::RegisterProbesSettingBackupPref(registry);
 
-  // Deprecated 04/2024.
-  registry->RegisterBooleanPref(kOmniboxInstantKeywordUsed, false);
-
-  // Deprecated 04/2024.
-  registry->RegisterDictionaryPref(kWebAppPreinstalledAppWindowExperiment);
-
-  // Deprecated 04/2024.
-  registry->RegisterTimePref(kDIPSTimerLastUpdate, base::Time());
-
 #if BUILDFLAG(IS_CHROMEOS)
-  // Deprecated 04/2024.
-  registry->RegisterBooleanPref(kMetricsUserInheritOwnerConsent, true);
-  registry->RegisterBooleanPref(kGlanceablesEnabled, true);
-
   // Deprecated 05/2024.
   registry->RegisterBooleanPref(kAccessibilityMouseKeysShortcutToPauseEnabled,
                                 true);
@@ -1547,7 +1522,12 @@
 
   // Deprecated 04/2025.
   registry->RegisterBooleanPref(kAddedBookmarkSincePowerBookmarksLaunch, false);
+
+  // Deprecated 04/2025.
   registry->RegisterIntegerPref(kGlicRolloutEligibility, 0);
+
+  // Deprecated 04/2025.
+  registry->RegisterListPref(kManagedAccessToGetAllScreensMediaAllowedForUrls);
 }
 
 }  // namespace
@@ -2518,20 +2498,7 @@
           password_manager_android_util::PasswordManagerUtilBridge>());
 #endif
 
-  // Added 04/2024.
-  profile_prefs->ClearPref(kOmniboxInstantKeywordUsed);
-
-  // Added 04/2024.
-  profile_prefs->ClearPref(kWebAppPreinstalledAppWindowExperiment);
-
-  // Added 04/2024.
-  profile_prefs->ClearPref(kDIPSTimerLastUpdate);
-
 #if BUILDFLAG(IS_CHROMEOS)
-  // Added 04/2024.
-  profile_prefs->ClearPref(kMetricsUserInheritOwnerConsent);
-  profile_prefs->ClearPref(kGlanceablesEnabled);
-
   // Added 05/2024.
   profile_prefs->ClearPref(kAccessibilityMouseKeysShortcutToPauseEnabled);
   profile_prefs->ClearPref(kAccessibilityMouseKeysDisableInTextFields);
@@ -2837,8 +2804,13 @@
 
   // Added 04/2025.
   profile_prefs->ClearPref(kAddedBookmarkSincePowerBookmarksLaunch);
+
+  // Added 04/2025.
   profile_prefs->ClearPref(kGlicRolloutEligibility);
 
+  // Added 04/2025
+  profile_prefs->ClearPref(kManagedAccessToGetAllScreensMediaAllowedForUrls);
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 
diff --git a/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_browsertest.cc b/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
index 704741c..21b7a2b 100644
--- a/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -1294,6 +1294,13 @@
   CheckCorrectForwardingResultMetric(
       histogram_tester,
       StreamingSearchPrefetchURLLoader::ForwardingResult::kCompleted, 1);
+
+  content::RenderFrameHost* frame = GetWebContents()->GetPrimaryMainFrame();
+  EXPECT_EQ(
+      "navigational-prefetch",
+      content::EvalJs(
+          frame, "performance.getEntriesByType('navigation')[0].deliveryType"));
+
   {
     ukm::SourceId ukm_source_id =
         GetWebContents()->GetPrimaryMainFrame()->GetPageUkmSourceId();
@@ -2253,6 +2260,11 @@
       "Omnibox.SearchPrefetch.ReceivedServableResponse2.Fallback."
       "SuggestionPrefetch",
       /*can_be_served*/ true, 1);
+  content::RenderFrameHost* frame = GetWebContents()->GetPrimaryMainFrame();
+  EXPECT_EQ(
+      "",
+      content::EvalJs(
+          frame, "performance.getEntriesByType('navigation')[0].deliveryType"));
 }
 
 IN_PROC_BROWSER_TEST_F(SearchPrefetchServiceEnabledBrowserTest,
diff --git a/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc b/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
index c7557ed..a582336f 100644
--- a/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
+++ b/chrome/browser/preloading/prefetch/search_prefetch/streaming_search_prefetch_url_loader.cc
@@ -509,6 +509,10 @@
     // Not safe to do anything after this point
   }
 
+  head->was_in_prefetch_cache = true;
+  head->navigation_delivery_type =
+      network::mojom::NavigationDeliveryType::kNavigationalPrefetch;
+
   if (forwarding_client_) {
     forwarding_client_->OnReceiveResponse(std::move(head), std::move(body),
                                           std::nullopt);
diff --git a/chrome/browser/privacy_sandbox/notice/mocks/mock_notice_storage.h b/chrome/browser/privacy_sandbox/notice/mocks/mock_notice_storage.h
index 510a711..12eb1aa 100644
--- a/chrome/browser/privacy_sandbox/notice/mocks/mock_notice_storage.h
+++ b/chrome/browser/privacy_sandbox/notice/mocks/mock_notice_storage.h
@@ -22,7 +22,7 @@
                notice::mojom::PrivacySandboxNoticeEvent),
               (override));
 
-  MOCK_METHOD(std::optional<PrivacySandboxNoticeData>,
+  MOCK_METHOD(std::optional<NoticeStorageData>,
               ReadNoticeData,
               (std::string_view notice),
               (const, override));
diff --git a/chrome/browser/privacy_sandbox/notice/notice_storage.cc b/chrome/browser/privacy_sandbox/notice/notice_storage.cc
index d450ce8..27d8d564 100644
--- a/chrome/browser/privacy_sandbox/notice/notice_storage.cc
+++ b/chrome/browser/privacy_sandbox/notice/notice_storage.cc
@@ -26,46 +26,59 @@
 using notice::mojom::PrivacySandboxNotice;
 using notice::mojom::PrivacySandboxNoticeEvent;
 
+constexpr int kCurrentSchemaVersion = 2;
+
 // Notice data will be saved as a dictionary in the PrefService of a profile.
 
 // PrefService path.
-constexpr char kPrivacySandboxNoticeDataPath[] = "privacy_sandbox.notices";
+constexpr char kNoticeDataPath[] = "privacy_sandbox.notices";
 
 // Unsynced pref that indicates the schema version this profile is using in
 // regards to the data model.
-constexpr char kPrivacySandboxSchemaVersion[] = "schema_version";
+constexpr char kSchemaVersionKey[] = "schema_version";
 
 // Unsynced pref that indicates the chrome version this profile was initially
 // shown the notice at. For migrated notices, this pref is empty.
-constexpr char kPrivacySandboxChromeVersion[] = "chrome_version";
+constexpr char kChromeVersionKey[] = "chrome_version";
 
 // Unsynced pref that indicates the events taken on the notice. Stored as a
 // sorted list in order of event performed containing dict entries.
-constexpr char kPrivacySandboxEvents[] = "events";
-
-// Deprecated. Do not use for new values.
-constexpr char kPrivacySandboxNoticeActionTaken[] = "notice_action_taken";
-
-// Deprecated. Do not use for new values.
-constexpr char kPrivacySandboxNoticeActionTakenTime[] =
-    "notice_action_taken_time";
-
-// Deprecated. Do not use for new values.
-constexpr char kPrivacySandboxNoticeLastShown[] = "notice_last_shown";
+constexpr char kEventsKey[] = "events";
 
 // Key value in the dict entry contained within `events`
-constexpr char kPrivacySandboxNoticeEvent[] = "event";
+constexpr char kEventKey[] = "event";
 
 // Key value in the dict entry contained within `events`
-constexpr char kPrivacySandboxNoticeEventTime[] = "timestamp";
+constexpr char kTimestampKey[] = "timestamp";
 
-constexpr int kPrivacySandboxNoticeSchemaVersion = 2;
+// V1 Fields - DEPRECATED
+constexpr char kNoticeActionTakenKey[] = "notice_action_taken";
+constexpr char kNoticeActionTakenTimeKey[] = "notice_action_taken_time";
+constexpr char kNoticeLastShownKey[] = "notice_last_shown";
 
 std::string CreatePrefPath(std::string_view notice,
                            std::string_view pref_name) {
   return base::StrCat({notice, ".", pref_name});
 }
 
+template <typename T>
+std::optional<T> ConvertTo(const base::Value::Dict* dict) {
+  if (!dict) {
+    return std::nullopt;
+  }
+  base::JSONValueConverter<T> converter;
+  T data;
+  if (converter.Convert(*dict, &data)) {
+    return data;
+  }
+  return std::nullopt;
+}
+
+template <typename T>
+std::optional<T> ConvertTo(const base::Value* value) {
+  return value ? ConvertTo<T>(value->GetIfDict()) : std::nullopt;
+}
+
 void CreateTimingHistogram(const std::string& name, base::TimeDelta sample) {
   base::UmaHistogramCustomTimes(name, sample, base::Milliseconds(1),
                                 base::Days(10), 100);
@@ -89,25 +102,23 @@
 }
 
 void SetSchemaVersion(PrefService* pref_service, std::string_view notice) {
-  ScopedDictPrefUpdate update(pref_service, kPrivacySandboxNoticeDataPath);
-  update.Get().SetByDottedPath(
-      CreatePrefPath(notice, kPrivacySandboxSchemaVersion),
-      kPrivacySandboxNoticeSchemaVersion);
+  ScopedDictPrefUpdate update(pref_service, kNoticeDataPath);
+  update.Get().SetByDottedPath(CreatePrefPath(notice, kSchemaVersionKey),
+                               kCurrentSchemaVersion);
 }
 
 base::Value::Dict BuildDictEntryEvent(PrivacySandboxNoticeEvent event,
                                       base::Time event_time) {
   base::Value::Dict params;
-  params.Set(kPrivacySandboxNoticeEvent, static_cast<int>(event));
-  params.Set(kPrivacySandboxNoticeEventTime, base::TimeToValue(event_time));
+  params.Set(kEventKey, static_cast<int>(event));
+  params.Set(kTimestampKey, base::TimeToValue(event_time));
   return params;
 }
 
 void SetChromeVersion(PrefService* pref_service, std::string_view notice) {
-  ScopedDictPrefUpdate update(pref_service, kPrivacySandboxNoticeDataPath);
-  update.Get().SetByDottedPath(
-      CreatePrefPath(notice, kPrivacySandboxChromeVersion),
-      version_info::GetVersionNumber());
+  ScopedDictPrefUpdate update(pref_service, kNoticeDataPath);
+  update.Get().SetByDottedPath(CreatePrefPath(notice, kChromeVersionKey),
+                               version_info::GetVersionNumber());
 }
 
 const Notice& FindNotice(NoticeId notice_id, NoticeCatalog* catalog) {
@@ -131,146 +142,82 @@
   return true;
 }
 
-bool MaybeValueToNoticeEvent(const base::Value* value,
-                             PrivacySandboxNoticeEvent* event) {
+template <typename T>
+bool MaybeValueToEnum(const base::Value* value, T* output) {
   if (!value || !value->is_int()) {
     return false;
   }
-  int notice_event = value->GetInt();
-  if (notice_event < static_cast<int>(PrivacySandboxNoticeEvent::kMinValue) ||
-      notice_event > static_cast<int>(PrivacySandboxNoticeEvent::kMaxValue)) {
+  int int_value = value->GetInt();
+  if (int_value < static_cast<int>(T::kMinValue) ||
+      int_value > static_cast<int>(T::kMaxValue)) {
     return false;
   }
-  *event = static_cast<PrivacySandboxNoticeEvent>(notice_event);
+  *output = static_cast<T>(int_value);
   return true;
 }
 
-std::optional<V1MigrationData> ExtractV1NoticeData(
-    PrefService* pref_service,
-    std::string_view notice,
-    const base::Value::Dict& data) {
-  std::optional<int> schema_version = data.FindIntByDottedPath(
-      CreatePrefPath(notice, kPrivacySandboxSchemaVersion));
-
-  if (!schema_version.has_value() || *schema_version != 1) {
-    return std::nullopt;
-  }
-
-  // Notice last shown.
-  std::optional<base::Time> shown_v1 = base::ValueToTime(data.FindByDottedPath(
-      CreatePrefPath(notice, kPrivacySandboxNoticeLastShown)));
-  V1MigrationData migration_data;
-  if (shown_v1.has_value()) {
-    migration_data.notice_last_shown = *shown_v1;
-  }
-
-  // Action taken.
-  std::optional<int> action_v1 = data.FindIntByDottedPath(
-      CreatePrefPath(notice, kPrivacySandboxNoticeActionTaken));
-  if (action_v1.has_value()) {
-    migration_data.notice_action_taken =
-        static_cast<NoticeActionTaken>(*action_v1);
-  }
-
-  // Action taken time.
-  std::optional<base::Time> action_time_v1 =
-      base::ValueToTime(data.FindByDottedPath(
-          CreatePrefPath(notice, kPrivacySandboxNoticeActionTakenTime)));
-  if (action_time_v1.has_value()) {
-    migration_data.notice_action_taken_time = *action_time_v1;
-  }
-
-  return migration_data;
-}
-
 void PopulateV2NoticeData(PrefService* pref_service,
                           std::string_view notice,
-                          const PrivacySandboxNoticeData& data) {
-  ScopedDictPrefUpdate update(pref_service, kPrivacySandboxNoticeDataPath);
-  update.Get().SetByDottedPath(
-      CreatePrefPath(notice, kPrivacySandboxSchemaVersion),
-      data.GetSchemaVersion());
+                          const NoticeStorageData& data) {
+  ScopedDictPrefUpdate update(pref_service, kNoticeDataPath);
+  update.Get().SetByDottedPath(CreatePrefPath(notice, kSchemaVersionKey),
+                               data.GetSchemaVersion());
 
   for (const auto& event : data.GetNoticeEvents()) {
     update.Get()
         .EnsureDict(notice)
-        ->EnsureList(kPrivacySandboxEvents)
+        ->EnsureList(kEventsKey)
         ->Append(
             BuildDictEntryEvent(event.get()->event, event.get()->timestamp));
   }
 }
 
-std::optional<PrivacySandboxNoticeData> ConvertToNoticeData(
-    const base::Value::Dict* notice_dict) {
-  if (!notice_dict) {
-    return std::nullopt;
-  }
-  base::JSONValueConverter<PrivacySandboxNoticeData> converter;
-  PrivacySandboxNoticeData notice_data;
-  if (converter.Convert(*notice_dict, &notice_data)) {
-    return notice_data;
-  } else {
-    return std::nullopt;
-  }
-}
-
-std::optional<PrivacySandboxNoticeData> ConvertToNoticeData(
-    const base::Value* notice_dict) {
-  if (!notice_dict) {
-    return std::nullopt;
-  }
-  return ConvertToNoticeData(notice_dict->GetIfDict());
-}
-
 }  // namespace
 
 void NoticeEventTimestampPair::RegisterJSONConverter(
     base::JSONValueConverter<NoticeEventTimestampPair>* converter) {
   converter->RegisterCustomValueField<PrivacySandboxNoticeEvent>(
-      kPrivacySandboxNoticeEvent, &NoticeEventTimestampPair::event,
-      &MaybeValueToNoticeEvent);
+      kEventKey, &NoticeEventTimestampPair::event,
+      &MaybeValueToEnum<PrivacySandboxNoticeEvent>);
   converter->RegisterCustomValueField<base::Time>(
-      kPrivacySandboxNoticeEventTime, &NoticeEventTimestampPair::timestamp,
-      &MaybeValueToTime);
+      kTimestampKey, &NoticeEventTimestampPair::timestamp, &MaybeValueToTime);
 }
 
 // PrivacySandboxNoticeData definitions.
-PrivacySandboxNoticeData::PrivacySandboxNoticeData() = default;
+NoticeStorageData::NoticeStorageData() = default;
 
-PrivacySandboxNoticeData::~PrivacySandboxNoticeData() = default;
+NoticeStorageData::~NoticeStorageData() = default;
 
-PrivacySandboxNoticeData::PrivacySandboxNoticeData(
-    PrivacySandboxNoticeData&& data) = default;
-PrivacySandboxNoticeData& PrivacySandboxNoticeData::operator=(
-    PrivacySandboxNoticeData&& data) = default;
+NoticeStorageData::NoticeStorageData(NoticeStorageData&& data) = default;
+NoticeStorageData& NoticeStorageData::operator=(NoticeStorageData&& data) =
+    default;
 
-int PrivacySandboxNoticeData::GetSchemaVersion() const {
+int NoticeStorageData::GetSchemaVersion() const {
   return schema_version_;
 }
-std::string PrivacySandboxNoticeData::GetChromeVersion() const {
+std::string NoticeStorageData::GetChromeVersion() const {
   return chrome_version_;
 }
 base::span<const std::unique_ptr<NoticeEventTimestampPair>>
-PrivacySandboxNoticeData::GetNoticeEvents() const {
+NoticeStorageData::GetNoticeEvents() const {
   return notice_events_;
 }
 
-void PrivacySandboxNoticeData::SetSchemaVersion(int schema_version) {
+void NoticeStorageData::SetSchemaVersion(int schema_version) {
   schema_version_ = schema_version;
 }
 
-void PrivacySandboxNoticeData::SetChromeVersion(
-    std::string_view chrome_version) {
+void NoticeStorageData::SetChromeVersion(std::string_view chrome_version) {
   chrome_version_ = chrome_version;
 }
 
-void PrivacySandboxNoticeData::SetNoticeEvents(
+void NoticeStorageData::SetNoticeEvents(
     std::vector<std::unique_ptr<NoticeEventTimestampPair>>&& events) {
   notice_events_ = std::move(events);
 }
 
-std::optional<base::Time>
-PrivacySandboxNoticeData::GetNoticeFirstShownFromEvents() const {
+std::optional<base::Time> NoticeStorageData::GetNoticeFirstShownFromEvents()
+    const {
   for (const auto& notice_event : notice_events_) {
     if (notice_event->event == PrivacySandboxNoticeEvent::kShown) {
       return notice_event->timestamp;
@@ -279,8 +226,8 @@
   return std::nullopt;
 }
 
-std::optional<base::Time>
-PrivacySandboxNoticeData::GetNoticeLastShownFromEvents() const {
+std::optional<base::Time> NoticeStorageData::GetNoticeLastShownFromEvents()
+    const {
   for (const auto& notice_event : base::Reversed(notice_events_)) {
     if (notice_event->event == PrivacySandboxNoticeEvent::kShown) {
       return notice_event->timestamp;
@@ -290,7 +237,7 @@
 }
 
 std::optional<NoticeEventTimestampPair>
-PrivacySandboxNoticeData::GetNoticeActionTakenForFirstShownFromEvents() const {
+NoticeStorageData::GetNoticeActionTakenForFirstShownFromEvents() const {
   std::optional<NoticeEventTimestampPair> notice_event_data;
   int last_shown_idx = 0;
   int first_notice_idx = 0;
@@ -306,24 +253,35 @@
   return notice_event_data;
 }
 
-void PrivacySandboxNoticeData::RegisterJSONConverter(
-    base::JSONValueConverter<PrivacySandboxNoticeData>* converter) {
-  converter->RegisterIntField(kPrivacySandboxSchemaVersion,
-                              &PrivacySandboxNoticeData::schema_version_);
-  converter->RegisterStringField(kPrivacySandboxChromeVersion,
-                                 &PrivacySandboxNoticeData::chrome_version_);
+void NoticeStorageData::RegisterJSONConverter(
+    base::JSONValueConverter<NoticeStorageData>* converter) {
+  converter->RegisterIntField(kSchemaVersionKey,
+                              &NoticeStorageData::schema_version_);
+  converter->RegisterStringField(kChromeVersionKey,
+                                 &NoticeStorageData::chrome_version_);
   converter->RegisterRepeatedMessage<NoticeEventTimestampPair>(
-      kPrivacySandboxEvents, &PrivacySandboxNoticeData::notice_events_);
+      kEventsKey, &NoticeStorageData::notice_events_);
 }
 
-// V1MigrationData definitions.
-V1MigrationData::V1MigrationData() = default;
-V1MigrationData::~V1MigrationData() = default;
+void V1MigrationData::RegisterJSONConverter(
+    base::JSONValueConverter<V1MigrationData>* converter) {
+  converter->RegisterIntField(kSchemaVersionKey,
+                              &V1MigrationData::schema_version);
+  converter->RegisterCustomValueField<NoticeActionTaken>(
+      kNoticeActionTakenKey, &V1MigrationData::notice_action_taken,
+      &MaybeValueToEnum<NoticeActionTaken>);
+  converter->RegisterCustomValueField<base::Time>(
+      kNoticeActionTakenTimeKey, &V1MigrationData::notice_action_taken_time,
+      &MaybeValueToTime);
+  converter->RegisterCustomValueField<base::Time>(
+      kNoticeLastShownKey, &V1MigrationData::notice_last_shown,
+      &MaybeValueToTime);
+}
 
 // PrivacySandboxNoticeStorage definitions.
 void PrivacySandboxNoticeStorage::RegisterProfilePrefs(
     PrefRegistrySimple* registry) {
-  registry->RegisterDictionaryPref(kPrivacySandboxNoticeDataPath);
+  registry->RegisterDictionaryPref(kNoticeDataPath);
 }
 
 // static
@@ -366,9 +324,9 @@
 }
 
 // static
-PrivacySandboxNoticeData PrivacySandboxNoticeStorage::ConvertV1SchemaToV2Schema(
+NoticeStorageData PrivacySandboxNoticeStorage::ToV2Schema(
     const V1MigrationData& data_v1) {
-  PrivacySandboxNoticeData data_v2;
+  NoticeStorageData data_v2;
   std::vector<std::unique_ptr<NoticeEventTimestampPair>> notice_events;
   data_v2.SetSchemaVersion(2);
 
@@ -396,7 +354,7 @@
     return;
   }
   const auto* notice_data_pref =
-      pref_service->GetUserPrefValue(kPrivacySandboxNoticeDataPath);
+      pref_service->GetUserPrefValue(kNoticeDataPath);
   if (!notice_data_pref) {
     return;
   }
@@ -408,22 +366,13 @@
   }
 
   for (const auto [notice, notice_value] : *data) {
-    const base::Value::Dict* notice_value_dict = notice_value.GetIfDict();
-    if (!notice_value_dict) {
+    auto data_v1 = ConvertTo<V1MigrationData>(&notice_value);
+    if (!data_v1 || data_v1->schema_version != 1) {
       continue;
     }
-    std::optional<int> schema_version =
-        notice_value_dict->FindInt(kPrivacySandboxSchemaVersion);
-    if (schema_version.has_value() && *schema_version == 2) {
-      continue;
-    }
+    PopulateV2NoticeData(pref_service, notice, ToV2Schema(*data_v1));
 
-    auto data_v1 = ExtractV1NoticeData(pref_service, notice, *data);
-    if (!data_v1) {
-      continue;
-    }
-    PrivacySandboxNoticeData data_v2 = ConvertV1SchemaToV2Schema(*data_v1);
-    PopulateV2NoticeData(pref_service, notice, data_v2);
+    // TODO(boujane) Erase V1 Only fields.
   }
 }
 
@@ -441,8 +390,8 @@
 
 void PrivacySandboxNoticeStorage::RecordStartupHistograms() const {
   for (const auto [notice, notice_value] :
-       pref_service_->GetDict(kPrivacySandboxNoticeDataPath)) {
-    auto notice_data = ConvertToNoticeData(&notice_value);
+       pref_service_->GetDict(kNoticeDataPath)) {
+    auto notice_data = ConvertTo<NoticeStorageData>(&notice_value);
 
     NoticeStartupState startup_state;
 
@@ -483,11 +432,10 @@
   }
 }
 
-std::optional<PrivacySandboxNoticeData>
-PrivacySandboxNoticeStorage::ReadNoticeData(std::string_view notice) const {
-  const base::Value::Dict& pref_data =
-      pref_service_->GetDict(kPrivacySandboxNoticeDataPath);
-  return ConvertToNoticeData(pref_data.FindDict(notice));
+std::optional<NoticeStorageData> PrivacySandboxNoticeStorage::ReadNoticeData(
+    std::string_view notice) const {
+  const base::Value::Dict& pref_data = pref_service_->GetDict(kNoticeDataPath);
+  return ConvertTo<NoticeStorageData>(pref_data.FindDict(notice));
 }
 
 void PrivacySandboxNoticeStorage::RecordEvent(
@@ -509,7 +457,7 @@
   CHECK(notice_action_taken != PrivacySandboxNoticeEvent::kShown)
       << "Use `SetNoticeShown` to set a kShown PrivacySandboxNoticeEvent "
          "instead.";
-  ScopedDictPrefUpdate update(pref_service_, kPrivacySandboxNoticeDataPath);
+  ScopedDictPrefUpdate update(pref_service_, kNoticeDataPath);
   auto notice_data = ReadNoticeData(notice);
 
   // The notice should be shown first before action can be taken on it.
@@ -546,7 +494,7 @@
       BuildDictEntryEvent(notice_action_taken, notice_action_taken_time);
   update.Get()
       .EnsureDict(notice)
-      ->EnsureList(kPrivacySandboxEvents)
+      ->EnsureList(kEventsKey)
       ->Append(std::move(entry));
 
   base::UmaHistogramEnumeration(
@@ -583,7 +531,7 @@
 }
 void PrivacySandboxNoticeStorage::SetNoticeShown(std::string_view notice,
                                                  base::Time notice_shown_time) {
-  ScopedDictPrefUpdate update(pref_service_, kPrivacySandboxNoticeDataPath);
+  ScopedDictPrefUpdate update(pref_service_, kNoticeDataPath);
   SetSchemaVersion(pref_service_, notice);
   SetChromeVersion(pref_service_, notice);
 
@@ -591,7 +539,7 @@
       BuildDictEntryEvent(PrivacySandboxNoticeEvent::kShown, notice_shown_time);
   update.Get()
       .EnsureDict(notice)
-      ->EnsureList(kPrivacySandboxEvents)
+      ->EnsureList(kEventsKey)
       ->Append(std::move(entry));
 
   // TODO(chrstne): Deprecate NoticeShown histogram once it is no longer used
diff --git a/chrome/browser/privacy_sandbox/notice/notice_storage.h b/chrome/browser/privacy_sandbox/notice/notice_storage.h
index 63c221f..7d4c5b2 100644
--- a/chrome/browser/privacy_sandbox/notice/notice_storage.h
+++ b/chrome/browser/privacy_sandbox/notice/notice_storage.h
@@ -49,6 +49,7 @@
 // for histograms.
 // LINT.IfChange(NoticeActionTaken)
 enum class NoticeActionTaken {
+  kMinValue = 0,
   // No Ack action set.
   kNotSet = 0,
   // ACK'ed the notice using 'GotIt' or some other form of acknowledgement.
@@ -99,14 +100,14 @@
   base::Time timestamp;
 };
 
-class PrivacySandboxNoticeData {
+class NoticeStorageData {
  public:
-  PrivacySandboxNoticeData();
-  ~PrivacySandboxNoticeData();
-  PrivacySandboxNoticeData& operator=(const PrivacySandboxNoticeData&) = delete;
-  PrivacySandboxNoticeData(const PrivacySandboxNoticeData& data) = delete;
-  PrivacySandboxNoticeData(PrivacySandboxNoticeData&& data);
-  PrivacySandboxNoticeData& operator=(PrivacySandboxNoticeData&& data);
+  NoticeStorageData();
+  ~NoticeStorageData();
+  NoticeStorageData& operator=(const NoticeStorageData&) = delete;
+  NoticeStorageData(const NoticeStorageData& data) = delete;
+  NoticeStorageData(NoticeStorageData&& data);
+  NoticeStorageData& operator=(NoticeStorageData&& data);
 
   int GetSchemaVersion() const;
   std::string GetChromeVersion() const;
@@ -134,7 +135,7 @@
   GetNoticeActionTakenForFirstShownFromEvents() const;
 
   static void RegisterJSONConverter(
-      base::JSONValueConverter<PrivacySandboxNoticeData>* converter);
+      base::JSONValueConverter<NoticeStorageData>* converter);
 
  private:
   int schema_version_ = 0;
@@ -144,11 +145,13 @@
 
 // Stores pre-migration interactions on a notice in the v1 schema.
 struct V1MigrationData {
-  V1MigrationData();
-  ~V1MigrationData();
+  int schema_version = 0;
   NoticeActionTaken notice_action_taken = NoticeActionTaken::kNotSet;
   base::Time notice_action_taken_time;
   base::Time notice_last_shown;
+
+  static void RegisterJSONConverter(
+      base::JSONValueConverter<V1MigrationData>* converter);
 };
 
 class NoticeStorage {
@@ -159,7 +162,7 @@
   // prefs aren't set. If an event is tracked but the event timestamp is
   // missing, return base::Time(). If an event timestamp is tracked but the
   // event itself is missing, return PrivacySandboxNoticeEvent::kUnknownAction.
-  virtual std::optional<PrivacySandboxNoticeData> ReadNoticeData(
+  virtual std::optional<NoticeStorageData> ReadNoticeData(
       std::string_view notice) const = 0;
 
   // Records histograms tracking the state of all notices.
@@ -179,7 +182,7 @@
 
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  std::optional<PrivacySandboxNoticeData> ReadNoticeData(
+  std::optional<NoticeStorageData> ReadNoticeData(
       std::string_view notice) const override;
 
   void RecordStartupHistograms() const override;
@@ -196,8 +199,7 @@
   static void UpdateNoticeSchemaV2(PrefService* pref_service);
 
   // Migrates fields in the notice data v1 schema to the notice data v2 schema.
-  static PrivacySandboxNoticeData ConvertV1SchemaToV2Schema(
-      const V1MigrationData& data_v1);
+  static NoticeStorageData ToV2Schema(const V1MigrationData& data_v1);
 
   // Converts the schema v1 NoticeActionTaken to the schema v2
   // PrivacySandboxNoticeEvent.
diff --git a/chrome/browser/privacy_sandbox/notice/notice_storage_fuzztest.cc b/chrome/browser/privacy_sandbox/notice/notice_storage_fuzztest.cc
index b601041..74c5931 100644
--- a/chrome/browser/privacy_sandbox/notice/notice_storage_fuzztest.cc
+++ b/chrome/browser/privacy_sandbox/notice/notice_storage_fuzztest.cc
@@ -24,8 +24,7 @@
   data_v1.notice_action_taken = notice_action_taken;
   data_v1.notice_action_taken_time = notice_taken_time;
   data_v1.notice_last_shown = notice_last_shown;
-  PrivacySandboxNoticeData data_v2 =
-      PrivacySandboxNoticeStorage::ConvertV1SchemaToV2Schema(data_v1);
+  NoticeStorageData data_v2 = PrivacySandboxNoticeStorage::ToV2Schema(data_v1);
   EXPECT_EQ(data_v2.GetSchemaVersion(), 2);
   auto notice_event = PrivacySandboxNoticeStorage::NoticeActionToNoticeEvent(
       notice_action_taken);
diff --git a/chrome/browser/privacy_sandbox/notice/notice_storage_unittest.cc b/chrome/browser/privacy_sandbox/notice/notice_storage_unittest.cc
index be40481..ecb695a 100644
--- a/chrome/browser/privacy_sandbox/notice/notice_storage_unittest.cc
+++ b/chrome/browser/privacy_sandbox/notice/notice_storage_unittest.cc
@@ -564,7 +564,7 @@
 class PrivacySandboxNoticeDataTest : public testing::Test {};
 
 TEST_F(PrivacySandboxNoticeDataTest, NoPrivacySandboxNoticeDataReturnsNothing) {
-  PrivacySandboxNoticeData data;
+  NoticeStorageData data;
   EXPECT_EQ(data.GetNoticeFirstShownFromEvents(), std::nullopt);
   EXPECT_EQ(data.GetNoticeLastShownFromEvents(), std::nullopt);
   EXPECT_EQ(data.GetNoticeActionTakenForFirstShownFromEvents(), std::nullopt);
@@ -572,7 +572,7 @@
 
 TEST_F(PrivacySandboxNoticeDataTest,
        NoticeShownEvent_AccessorReturnsFirstShownSuccessfully) {
-  PrivacySandboxNoticeData data;
+  NoticeStorageData data;
   data.SetNoticeEvents(BuildEvents({
       {kShown, 100},
       {kAck, 150},
@@ -585,7 +585,7 @@
 
 TEST_F(PrivacySandboxNoticeDataTest,
        NoticeShownEvent_AccessorReturnsLastShownSuccessfully) {
-  PrivacySandboxNoticeData data;
+  NoticeStorageData data;
   data.SetNoticeEvents(BuildEvents({
       {kShown, 100},
       {kAck, 150},
@@ -598,7 +598,7 @@
 
 TEST_F(PrivacySandboxNoticeDataTest,
        NoNoticeActionTakenEvent_AccessorReturnsNoValue) {
-  PrivacySandboxNoticeData data;
+  NoticeStorageData data;
   data.SetNoticeEvents(BuildEvents({
       {kShown, 100},
       {kShown, 200},
@@ -609,7 +609,7 @@
 
 TEST_F(PrivacySandboxNoticeDataTest,
        NoticeActionTakenEvent_AccessorReturnsActionSuccessfully) {
-  PrivacySandboxNoticeData data;
+  NoticeStorageData data;
   data.SetNoticeEvents(BuildEvents({
       {kShown, 100},
       {kAck, 120},
@@ -625,7 +625,7 @@
 TEST_F(
     PrivacySandboxNoticeDataTest,
     NoticeActionTakenEvent_AccessorReturnsActionSuccessfullyMultipleActions) {
-  PrivacySandboxNoticeData data;
+  NoticeStorageData data;
   data.SetNoticeEvents(BuildEvents({
       {kShown, 100},
       {kAck, 120},
@@ -642,7 +642,7 @@
 TEST_F(
     PrivacySandboxNoticeDataTest,
     NoticeActionTakenEvent_AccessorReturnsActionSuccessfullyWithMultipleShownValues) {
-  PrivacySandboxNoticeData data;
+  NoticeStorageData data;
   data.SetNoticeEvents(BuildEvents({
       {kShown, 100},
       {kShown, 110},
diff --git a/chrome/browser/resources/settings/autofill_page/address_edit_dialog.html b/chrome/browser/resources/settings/autofill_page/address_edit_dialog.html
index ff748c0..82d398fa9 100644
--- a/chrome/browser/resources/settings/autofill_page/address_edit_dialog.html
+++ b/chrome/browser/resources/settings/autofill_page/address_edit_dialog.html
@@ -3,6 +3,11 @@
         white-space: nowrap;
       }
 
+      :host-context([dir=rtl]) cr-input.phone-number-input::part(input) {
+        direction: ltr;
+        text-align: end;
+      }
+
       .address-row {
         display: flex;
       }
@@ -103,7 +108,8 @@
                 <cr-input type="text" label="[[item.label]]"
                     value="{{item.value}}" spellcheck="false"
                     maxlength="1000"
-                    class$="address-column [[item.additionalClassName]]"
+                    class$="address-column [[item.additionalClassName]]
+                    [[getPhoneNumberInputClass_(item.fieldType)]]"
                     required="[[item.isRequired]]"
                     invalid="[[isVisuallyInvalid_(item.isValidatable,
                                                   item.isValid)]]">
diff --git a/chrome/browser/resources/settings/autofill_page/address_edit_dialog.ts b/chrome/browser/resources/settings/autofill_page/address_edit_dialog.ts
index c2b53e0..d3118f2e 100644
--- a/chrome/browser/resources/settings/autofill_page/address_edit_dialog.ts
+++ b/chrome/browser/resources/settings/autofill_page/address_edit_dialog.ts
@@ -292,6 +292,15 @@
     return !country.countryCode;
   }
 
+  private getPhoneNumberInputClass_(
+    fieldType: chrome.autofillPrivate.FieldType): string {
+  if (fieldType ===
+      chrome.autofillPrivate.FieldType.PHONE_HOME_WHOLE_NUMBER) {
+    return 'phone-number-input';
+  }
+  return '';
+}
+
   private isAddressStoredInAccount_(): boolean {
     if (this.address.guid) {
       return this.address.metadata !== undefined &&
diff --git a/chrome/browser/sync/test/integration/select_type_and_migrate_local_data_items_when_active_sync_test.cc b/chrome/browser/sync/test/integration/select_type_and_migrate_local_data_items_when_active_sync_test.cc
index 22e2b41..be34f6a 100644
--- a/chrome/browser/sync/test/integration/select_type_and_migrate_local_data_items_when_active_sync_test.cc
+++ b/chrome/browser/sync/test/integration/select_type_and_migrate_local_data_items_when_active_sync_test.cc
@@ -59,14 +59,19 @@
  public:
   SelectTypeAndMigrateLocalDataItemsWhenActiveTest()
       : SyncTest(SINGLE_CLIENT),
-        address_(autofill::test::GetFullProfile()),
         password_(CreateTestPasswordForm(0)) {
     feature_list_.InitWithFeatures(
         /*enabled_features=*/
         {switches::kImprovedSigninUIOnDesktop,
-         switches::kSyncEnableBookmarksInTransportMode},
+         switches::kSyncEnableBookmarksInTransportMode,
+         autofill::features::kAutofillSupportLastNamePrefix},
         /*disabled_features=*/{
             syncer::kSyncEnableContactInfoDataTypeForCustomPassphraseUsers});
+
+    // Ensure profile creation occurs after flag initialization to guarantee
+    // their effectiveness within the profile's constructor.
+    address_ =
+        std::make_unique<AutofillProfile>(autofill::test::GetFullProfile());
   }
 
   // In SINGLE_CLIENT tests, there's only a single PersonalDataManager.
@@ -74,7 +79,7 @@
     return contact_info_helper::GetPersonalDataManager(GetProfile(0));
   }
 
-  const AutofillProfile& address() { return address_; }
+  const AutofillProfile& address() { return *address_; }
   const PasswordForm& password() { return password_; }
 
   // Sign in with `signin::ConsentLevel::kSignin`.
@@ -89,10 +94,10 @@
   }
 
   void SaveLocalAddress() {
-    GetPersonalDataManager()->address_data_manager().AddProfile(address_);
+    GetPersonalDataManager()->address_data_manager().AddProfile(*address_);
     EXPECT_TRUE(AddressDataManagerProfileChecker(
                     &GetPersonalDataManager()->address_data_manager(),
-                    UnorderedElementsAre(address_))
+                    UnorderedElementsAre(*address_))
                     .Wait());
   }
 
@@ -121,7 +126,7 @@
 
  private:
   base::test::ScopedFeatureList feature_list_;
-  AutofillProfile address_;
+  std::unique_ptr<AutofillProfile> address_;
   PasswordForm password_;
 };
 
diff --git a/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc b/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc
index f282a9e..2d07587 100644
--- a/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc
@@ -1861,4 +1861,64 @@
 
 #endif  // BUILDFLAG(ENABLE_GLIC)
 
+class SingleClientDecouplePriorityPreferencesSyncTestWithFlagDisabled
+    : public SingleClientPreferencesWithAccountStorageSyncTest {
+ public:
+  SingleClientDecouplePriorityPreferencesSyncTestWithFlagDisabled() {
+    feature_list_.InitAndDisableFeature(
+        syncer::kSyncSupportAlwaysSyncingPriorityPreferences);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    SingleClientDecouplePriorityPreferencesSyncTestWithFlagDisabled,
+    InactiveWhenUserToggleIsOff) {
+  ASSERT_TRUE(SetupClients());
+  ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().empty());
+
+  ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
+  // Disable all user selectable types.
+  ASSERT_TRUE(GetClient(0)->DisableSyncForAllDatatypes());
+  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
+
+  // User toggle is off.
+  ASSERT_FALSE(GetSyncService(0)->GetUserSettings()->GetSelectedTypes().Has(
+      syncer::UserSelectableType::kPreferences));
+  // Preferences and Priority preferences are inactive.
+  ASSERT_FALSE(
+      GetSyncService(0)->GetActiveDataTypes().Has(syncer::PREFERENCES));
+  EXPECT_FALSE(GetSyncService(0)->GetActiveDataTypes().Has(
+      syncer::PRIORITY_PREFERENCES));
+}
+
+class SingleClientDecouplePriorityPreferencesSyncTest
+    : public SingleClientPreferencesWithAccountStorageSyncTest {
+  base::test::ScopedFeatureList feature_list_{
+      syncer::kSyncSupportAlwaysSyncingPriorityPreferences};
+};
+
+IN_PROC_BROWSER_TEST_F(SingleClientDecouplePriorityPreferencesSyncTest,
+                       ActiveWhenUserToggleIsOff) {
+  ASSERT_TRUE(SetupClients());
+  ASSERT_TRUE(GetSyncService(0)->GetActiveDataTypes().empty());
+
+  ASSERT_TRUE(GetClient(0)->SignInPrimaryAccount());
+  // Disable all user selectable types.
+  ASSERT_TRUE(GetClient(0)->DisableSyncForAllDatatypes());
+  ASSERT_TRUE(GetClient(0)->AwaitSyncTransportActive());
+
+  // User toggle is off.
+  ASSERT_FALSE(GetSyncService(0)->GetUserSettings()->GetSelectedTypes().Has(
+      syncer::UserSelectableType::kPreferences));
+  // Preferences is inactive.
+  ASSERT_FALSE(
+      GetSyncService(0)->GetActiveDataTypes().Has(syncer::PREFERENCES));
+  // Priority preferences is still active.
+  EXPECT_TRUE(GetSyncService(0)->GetActiveDataTypes().Has(
+      syncer::PRIORITY_PREFERENCES));
+}
+
 }  // namespace
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index d03dee2f..17d00f0 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3043,6 +3043,7 @@
       "//chrome/browser/ui/autofill/payments",
       "//chrome/browser/ui/lens",
       "//chrome/browser/ui/lens:impl",
+      "//chrome/browser/ui/views/privacy_sandbox",
       "//components/lens/proto/server:proto",
       "//third_party/lens_server_proto:lens_overlay_proto",
       "//third_party/omnibox_proto",
diff --git a/chrome/browser/ui/android/autofill/save_update_address_profile_flow_manager_browsertest.cc b/chrome/browser/ui/android/autofill/save_update_address_profile_flow_manager_browsertest.cc
index 177ae5d..8d9b8745 100644
--- a/chrome/browser/ui/android/autofill/save_update_address_profile_flow_manager_browsertest.cc
+++ b/chrome/browser/ui/android/autofill/save_update_address_profile_flow_manager_browsertest.cc
@@ -30,12 +30,6 @@
   SaveUpdateAddressProfileFlowManagerBrowserTest() = default;
   ~SaveUpdateAddressProfileFlowManagerBrowserTest() override = default;
 
-  void SetUp() override {
-    AndroidBrowserTest::SetUp();
-    profile_ = test::GetFullProfile();
-    original_profile_ = test::GetFullProfile2();
-  }
-
   // AndroidBrowserTest:
   void SetUpOnMainThread() override {
     flow_manager_ = std::make_unique<SaveUpdateAddressProfileFlowManager>();
@@ -58,17 +52,15 @@
     return !!flow_manager_->GetPromptControllerForTest();
   }
 
-  AutofillProfile profile_{
-      autofill::i18n_model_definition::kLegacyHierarchyCountryCode};
-  AutofillProfile original_profile_{
-      autofill::i18n_model_definition::kLegacyHierarchyCountryCode};
   std::unique_ptr<SaveUpdateAddressProfileFlowManager> flow_manager_;
 };
 
 IN_PROC_BROWSER_TEST_F(SaveUpdateAddressProfileFlowManagerBrowserTest,
                        TriggerAutoDeclineDecisionIfMessageIsDisplayed) {
-  flow_manager_->OfferSave(GetWebContents(), profile_, &original_profile_,
-                           kNotMigrationToAccount,
+  AutofillProfile submitted_profile = test::GetFullProfile();
+  AutofillProfile original_profile = test::GetFullProfile2();
+  flow_manager_->OfferSave(GetWebContents(), submitted_profile,
+                           &original_profile, kNotMigrationToAccount,
                            /*callback=*/base::DoNothing());
   EXPECT_TRUE(IsMessageDisplayed());
   EXPECT_FALSE(IsPromptDisplayed());
@@ -86,8 +78,10 @@
 
 IN_PROC_BROWSER_TEST_F(SaveUpdateAddressProfileFlowManagerBrowserTest,
                        TriggerAutoDeclineDecisionIfPromptIsDisplayed) {
-  flow_manager_->OfferSave(GetWebContents(), profile_, &original_profile_,
-                           kNotMigrationToAccount,
+  AutofillProfile submitted_profile = test::GetFullProfile();
+  AutofillProfile original_profile = test::GetFullProfile2();
+  flow_manager_->OfferSave(GetWebContents(), submitted_profile,
+                           &original_profile, kNotMigrationToAccount,
                            /*callback=*/base::DoNothing());
   // Proceed with message to prompt.
   flow_manager_->GetMessageControllerForTest()->OnPrimaryAction();
diff --git a/chrome/browser/ui/android/hats/hats_service_android.cc b/chrome/browser/ui/android/hats/hats_service_android.cc
index 24b7e64..9754df5 100644
--- a/chrome/browser/ui/android/hats/hats_service_android.cc
+++ b/chrome/browser/ui/android/hats/hats_service_android.cc
@@ -39,6 +39,7 @@
     content::WebContents* web_contents,
     const SurveyBitsData& product_specific_bits_data,
     const SurveyStringData& product_specific_string_data,
+    NavigationBehaviour navigation_behaviour,
     base::OnceClosure success_callback,
     base::OnceClosure failure_callback,
     const std::optional<std::string>& supplied_trigger_id,
@@ -47,6 +48,7 @@
       trigger_(trigger),
       product_specific_bits_data_(product_specific_bits_data),
       product_specific_string_data_(product_specific_string_data),
+      navigation_behaviour_(navigation_behaviour),
       success_callback_(std::move(success_callback)),
       failure_callback_(std::move(failure_callback)),
       supplied_trigger_id_(supplied_trigger_id),
@@ -135,6 +137,19 @@
   hats_service_->RemoveTask(*this);
 }
 
+void HatsServiceAndroid::DelayedSurveyTask::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (hats_service_->IsNavigationAllowed(navigation_handle,
+                                         navigation_behaviour_)) {
+    return;
+  }
+
+  if (!failure_callback_.is_null()) {
+    std::move(failure_callback_).Run();
+  }
+  hats_service_->RemoveTask(*this);
+}
+
 void HatsServiceAndroid::DelayedSurveyTask::WebContentsDestroyed() {
   if (!failure_callback_.is_null()) {
     std::move(failure_callback_).Run();
@@ -202,9 +217,6 @@
     const std::optional<std::string>& supplied_trigger_id,
     const SurveyOptions& survey_options) {
   CHECK(web_contents);
-  CHECK(navigation_behaviour ==
-        NavigationBehaviour::ALLOW_ANY);  // Currently only ALLOW_ANY is
-                                          // supported on Android
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (survey_configs_by_triggers_.find(trigger) ==
       survey_configs_by_triggers_.end()) {
@@ -216,8 +228,9 @@
   }
   auto result = pending_tasks_.emplace(
       this, trigger, web_contents, product_specific_bits_data,
-      product_specific_string_data, std::move(success_callback),
-      std::move(failure_callback), supplied_trigger_id, survey_options);
+      product_specific_string_data, navigation_behaviour,
+      std::move(success_callback), std::move(failure_callback),
+      supplied_trigger_id, survey_options);
   if (!result.second) {
     return false;
   }
@@ -268,3 +281,7 @@
 void HatsServiceAndroid::RemoveTask(const DelayedSurveyTask& task) {
   pending_tasks_.erase(task);
 }
+
+bool HatsServiceAndroid::HasPendingTasksForTesting() {
+  return !pending_tasks_.empty();
+}
diff --git a/chrome/browser/ui/android/hats/hats_service_android.h b/chrome/browser/ui/android/hats/hats_service_android.h
index d06b8c8..fa01441 100644
--- a/chrome/browser/ui/android/hats/hats_service_android.h
+++ b/chrome/browser/ui/android/hats/hats_service_android.h
@@ -42,6 +42,7 @@
                       content::WebContents* web_contents,
                       const SurveyBitsData& product_specific_bits_data,
                       const SurveyStringData& product_specific_string_data,
+                      NavigationBehaviour navigation_behaviour,
                       base::OnceClosure success_callback,
                       base::OnceClosure failure_callback,
                       const std::optional<std::string>& supplied_trigger_id,
@@ -60,6 +61,8 @@
     void DismissCallback(messages::DismissReason reason);
 
     // content::WebContentsObserver
+    void DidFinishNavigation(
+        content::NavigationHandle* navigation_handle) override;
     void WebContentsDestroyed() override;
 
     // Returns a weak pointer to this object.
@@ -79,6 +82,7 @@
     std::string trigger_;
     SurveyBitsData product_specific_bits_data_;
     SurveyStringData product_specific_string_data_;
+    NavigationBehaviour navigation_behaviour_;
     base::OnceClosure success_callback_;
     base::OnceClosure failure_callback_;
     std::optional<std::string> supplied_trigger_id_;
@@ -143,6 +147,9 @@
       const SurveyBitsData& product_specific_bits_data,
       const SurveyStringData& product_specific_string_data) override;
 
+  // Surface the overloaded method from the base class.
+  using HatsService::LaunchDelayedSurveyForWebContents;
+
   bool LaunchDelayedSurveyForWebContents(
       const std::string& trigger,
       content::WebContents* web_contents,
@@ -167,6 +174,8 @@
     return const_cast<DelayedSurveyTask&>(*pending_tasks_.begin());
   }
 
+  bool HasPendingTasksForTesting();
+
  protected:
   // Remove |task| from the set of |pending_tasks_|.
   void RemoveTask(const DelayedSurveyTask& task);
diff --git a/chrome/browser/ui/android/hats/hats_service_android_browsertest.cc b/chrome/browser/ui/android/hats/hats_service_android_browsertest.cc
new file mode 100644
index 0000000..357a40f
--- /dev/null
+++ b/chrome/browser/ui/android/hats/hats_service_android_browsertest.cc
@@ -0,0 +1,171 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/android/hats/hats_service_android.h"
+
+#include <memory>
+
+#include "base/functional/bind.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
+#include "chrome/test/base/android/android_browser_test.h"
+#include "chrome/test/base/chrome_test_utils.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace hats {
+
+namespace {
+
+const char kTestSurveyTrigger[] = "testing";
+
+class SurveyObserver {
+ public:
+  SurveyObserver() = default;
+
+  void Accept() { accepted_ = true; }
+
+  void Dismiss() { dismissed_ = true; }
+
+  bool IsAccepted() { return accepted_; }
+
+  bool IsDismissed() { return dismissed_; }
+
+  base::WeakPtr<SurveyObserver> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+ private:
+  bool accepted_ = false;
+  bool dismissed_ = false;
+
+  base::WeakPtrFactory<SurveyObserver> weak_ptr_factory_{this};
+};
+
+}  // namespace
+
+class HatsServiceAndroidBrowserTest : public AndroidBrowserTest {
+ public:
+  HatsServiceAndroidBrowserTest() = default;
+  ~HatsServiceAndroidBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+  content::WebContents* web_contents() {
+    return chrome_test_utils::GetActiveWebContents(this);
+  }
+
+  HatsServiceAndroid* GetHatsService() {
+    HatsServiceAndroid* service =
+        static_cast<HatsServiceAndroid*>(HatsServiceFactory::GetForProfile(
+            Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
+            true));
+    return service;
+  }
+
+  bool NavigateTo(std::string_view hostname, std::string_view relative_url) {
+    auto* web_contents = this->web_contents();
+    content::TestNavigationObserver observer(web_contents);
+    bool is_same_url = content::NavigateToURL(
+        web_contents, embedded_test_server()->GetURL(hostname, relative_url));
+    observer.Wait();
+    return is_same_url;
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(HatsServiceAndroidBrowserTest,
+                       NavigationBehavior_AllowAny) {
+  SurveyObserver observer;
+
+  ASSERT_TRUE(NavigateTo("a.test", "/empty.html"));
+
+  GetHatsService()->LaunchDelayedSurveyForWebContents(
+      kTestSurveyTrigger, web_contents(), 42000, {}, {},
+      /*navigation_behaviour=*/
+      HatsService::NavigationBehaviour::ALLOW_ANY,
+      /*success_callback=*/
+      base::BindOnce(&SurveyObserver::Accept, observer.GetWeakPtr()),
+      /*failure_callback=*/
+      base::BindOnce(&SurveyObserver::Dismiss, observer.GetWeakPtr()));
+
+  EXPECT_TRUE(GetHatsService()->HasPendingTasksForTesting());
+
+  ASSERT_TRUE(NavigateTo("b.test", "/empty.html"));
+
+  EXPECT_TRUE(GetHatsService()->HasPendingTasksForTesting());
+  EXPECT_FALSE(observer.IsAccepted());
+  EXPECT_FALSE(observer.IsDismissed());
+}
+
+IN_PROC_BROWSER_TEST_F(HatsServiceAndroidBrowserTest,
+                       NavigationBehavior_RequireSameOrigin) {
+  SurveyObserver observer;
+
+  ASSERT_TRUE(NavigateTo("a.test", "/empty.html"));
+
+  GetHatsService()->LaunchDelayedSurveyForWebContents(
+      kTestSurveyTrigger, web_contents(), 42000, {}, {},
+      /*navigation_behaviour=*/
+      HatsService::NavigationBehaviour::REQUIRE_SAME_ORIGIN,
+      /*success_callback=*/
+      base::BindOnce(&SurveyObserver::Accept, observer.GetWeakPtr()),
+      /*failure_callback=*/
+      base::BindOnce(&SurveyObserver::Dismiss, observer.GetWeakPtr()));
+
+  EXPECT_TRUE(GetHatsService()->HasPendingTasksForTesting());
+
+  ASSERT_TRUE(NavigateTo("a.test", "/empty_script.html"));
+
+  EXPECT_TRUE(GetHatsService()->HasPendingTasksForTesting());
+  EXPECT_FALSE(observer.IsAccepted());
+  EXPECT_FALSE(observer.IsDismissed());
+
+  ASSERT_TRUE(NavigateTo("b.test", "/empty.html"));
+
+  EXPECT_FALSE(GetHatsService()->HasPendingTasksForTesting());
+  EXPECT_FALSE(observer.IsAccepted());
+  EXPECT_TRUE(observer.IsDismissed());
+}
+
+IN_PROC_BROWSER_TEST_F(HatsServiceAndroidBrowserTest,
+                       NavigationBehavior_RequireSameDocument) {
+  SurveyObserver observer;
+
+  ASSERT_TRUE(NavigateTo("a.test", "/empty.html"));
+
+  GetHatsService()->LaunchDelayedSurveyForWebContents(
+      kTestSurveyTrigger, web_contents(), 42000, {}, {},
+      /*navigation_behaviour=*/
+      HatsService::NavigationBehaviour::REQUIRE_SAME_DOCUMENT,
+      /*success_callback=*/
+      base::BindOnce(&SurveyObserver::Accept, observer.GetWeakPtr()),
+      /*failure_callback=*/
+      base::BindOnce(&SurveyObserver::Dismiss, observer.GetWeakPtr()));
+
+  EXPECT_TRUE(GetHatsService()->HasPendingTasksForTesting());
+
+  // Same-document navigation
+  web_contents()->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
+      u"document.location='#';", base::NullCallback(),
+      content::ISOLATED_WORLD_ID_GLOBAL);
+  content::RunAllTasksUntilIdle();
+
+  EXPECT_TRUE(GetHatsService()->HasPendingTasksForTesting());
+  EXPECT_FALSE(observer.IsAccepted());
+  EXPECT_FALSE(observer.IsDismissed());
+
+  ASSERT_TRUE(NavigateTo("a.test", "/empty_script.html"));
+
+  EXPECT_FALSE(GetHatsService()->HasPendingTasksForTesting());
+  EXPECT_FALSE(observer.IsAccepted());
+  EXPECT_TRUE(observer.IsDismissed());
+}
+
+}  // namespace hats
diff --git a/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc b/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
index fc0947f..bcefe67 100644
--- a/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
+++ b/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
@@ -20,7 +20,9 @@
 #include "net/dns/mock_host_resolver.h"
 
 namespace hats {
+
 namespace {
+
 const char kTestSurveyTrigger[] = "testing";
 const SurveyBitsData kTestSurveyProductSpecificBitsData{
     {"Test Field 1", true},
diff --git a/chrome/browser/ui/android/hats/survey_client_android_unittest.cc b/chrome/browser/ui/android/hats/survey_client_android_unittest.cc
index 68bb3f2..cc803a9 100644
--- a/chrome/browser/ui/android/hats/survey_client_android_unittest.cc
+++ b/chrome/browser/ui/android/hats/survey_client_android_unittest.cc
@@ -24,7 +24,9 @@
 using base::android::ScopedJavaGlobalRef;
 
 namespace hats {
+
 namespace {
+
 const char kTestSurveyTrigger[] = "testing";
 const SurveyBitsData kTestSurveyProductSpecificBitsData{
     {"Test Field 1", true},
diff --git a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
index 2f0d4c2..ef099fc 100644
--- a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
+++ b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
@@ -40,6 +40,9 @@
     /** Whether bright theme colors are allowed. */
     private final boolean mAllowBrightThemeColors;
 
+    /** Whether tab theming is allowed on large screens */
+    private final boolean mAllowThemingOnTablets;
+
     /** Whether or not the default color is used. */
     private boolean mIsDefaultColorUsed;
 
@@ -51,6 +54,7 @@
      * @param allowThemingInNightMode Whether the tab theme should be used when the device is in
      *     night mode.
      * @param allowBrightThemeColors Whether the tab allows bright theme colors.
+     * @param allowThemingOnTablets Whether the tab them should be used on large form-factors.
      */
     public TopUiThemeColorProvider(
             Context context,
@@ -58,7 +62,8 @@
             Supplier<Integer> activityThemeColorSupplier,
             boolean isTablet,
             boolean allowThemingInNightMode,
-            boolean allowBrightThemeColors) {
+            boolean allowBrightThemeColors,
+            boolean allowThemingOnTablets) {
         super(context);
         mContext = context;
         mTabObserver =
@@ -77,6 +82,7 @@
         mIsTablet = isTablet;
         mAllowThemingInNightMode = allowThemingInNightMode;
         mAllowBrightThemeColors = allowBrightThemeColors;
+        mAllowThemingOnTablets = allowThemingOnTablets;
     }
 
     /**
@@ -170,9 +176,10 @@
     private boolean isThemingAllowed(Tab tab) {
         boolean disallowDueToNightMode =
                 !mAllowThemingInNightMode && ColorUtils.inNightMode(tab.getContext());
+        final boolean isEligibleFormFactor = mAllowThemingOnTablets || !mIsTablet;
 
         return tab.isThemingAllowed()
-                && !mIsTablet
+                && isEligibleFormFactor
                 && !disallowDueToNightMode
                 && !tab.isNativePage()
                 && !tab.isIncognito();
diff --git a/chrome/browser/ui/autofill/address_bubbles_controller_interactive_uitest.cc b/chrome/browser/ui/autofill/address_bubbles_controller_interactive_uitest.cc
index 25fa316..37656e9 100644
--- a/chrome/browser/ui/autofill/address_bubbles_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/autofill/address_bubbles_controller_interactive_uitest.cc
@@ -14,6 +14,7 @@
 #include "components/autofill/core/browser/data_model/addresses/autofill_profile_test_api.h"
 #include "components/autofill/core/browser/foundations/autofill_client.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "content/public/test/browser_test.h"
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/views/bubble/bubble_frame_view.h"
@@ -60,6 +61,8 @@
   // or Cancel the prompt, it is set in the AddressProfileSavePromptCallback
   // passed to the prompt.
   AutofillClient::AddressPromptUserDecision user_decision_;
+  base::test::ScopedFeatureList feature_list_{
+      features::kAutofillSupportLastNamePrefix};
 };
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/autofill/autofill_field_promo_controller_impl.cc b/chrome/browser/ui/autofill/autofill_field_promo_controller_impl.cc
index 73e4d100c..4d46b558 100644
--- a/chrome/browser/ui/autofill/autofill_field_promo_controller_impl.cc
+++ b/chrome/browser/ui/autofill/autofill_field_promo_controller_impl.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/ui/user_education/browser_user_education_interface.h"
 #include "components/autofill/content/browser/content_autofill_driver.h"
 #include "components/autofill/core/browser/suggestions/suggestion_hiding_reason.h"
+#include "components/autofill_ai/core/browser/autofill_ai_metrics.h"
 #include "components/password_manager/content/browser/content_password_manager_driver.h"
 #include "content/public/browser/web_contents.h"
 #include "ui/gfx/geometry/rect_f.h"
@@ -93,6 +94,9 @@
   // On failure to show, hide the invisible view.
   if (!result) {
     Hide();
+  } else if (feature_promo_ == feature_engagement::kIPHAutofillAiOptInFeature) {
+    autofill_ai::LogOptInFunnelEvent(
+        autofill_ai::AutofillAiOptInFunnelEvents::kIphShown);
   }
 }
 
diff --git a/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl_interactive_uitest.cc b/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl_interactive_uitest.cc
index 3af5d3361..63ed3c9 100644
--- a/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl_interactive_uitest.cc
+++ b/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl_interactive_uitest.cc
@@ -7,6 +7,7 @@
 #include "chrome/test/interaction/interactive_browser_test.h"
 #include "components/autofill/core/browser/data_model/addresses/autofill_profile.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "ui/views/window/dialog_client_view.h"
@@ -89,6 +90,8 @@
   }
 
  private:
+  base::test::ScopedFeatureList feature_list_{
+    features::kAutofillSupportLastNamePrefix};
   // The latest user decisive interaction with the editor, e.g. Save or Cancel
   // the editor, it is set in the AddressProfileSavePromptCallback passed to the
   // prompt.
diff --git a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc
index 21f9e4e..c7f3cdb 100644
--- a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc
+++ b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.cc
@@ -558,11 +558,12 @@
 }
 
 void ChromePaymentsAutofillClient::ShowCardUnmaskOtpInputDialog(
+    CreditCard::RecordType card_type,
     const CardUnmaskChallengeOption& challenge_option,
     base::WeakPtr<OtpUnmaskDelegate> delegate) {
   card_unmask_otp_input_dialog_controller_ =
-      std::make_unique<CardUnmaskOtpInputDialogControllerImpl>(challenge_option,
-                                                               delegate);
+      std::make_unique<CardUnmaskOtpInputDialogControllerImpl>(
+          card_type, challenge_option, delegate);
   card_unmask_otp_input_dialog_controller_->ShowDialog(
       base::BindOnce(&CreateAndShowOtpInputDialog,
                      card_unmask_otp_input_dialog_controller_->GetWeakPtr(),
diff --git a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.h b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.h
index 80996d0..1dc4fc51 100644
--- a/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.h
+++ b/chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.h
@@ -152,6 +152,7 @@
       bool show_confirmation_before_closing,
       base::OnceClosure no_interactive_authentication_callback) override;
   void ShowCardUnmaskOtpInputDialog(
+      CreditCard::RecordType card_type,
       const CardUnmaskChallengeOption& challenge_option,
       base::WeakPtr<OtpUnmaskDelegate> delegate) override;
   void OnUnmaskOtpVerificationResult(OtpUnmaskResult unmask_result) override;
diff --git a/chrome/browser/ui/feature_first_run/BUILD.gn b/chrome/browser/ui/feature_first_run/BUILD.gn
index 38d1483c..f5cc473 100644
--- a/chrome/browser/ui/feature_first_run/BUILD.gn
+++ b/chrome/browser/ui/feature_first_run/BUILD.gn
@@ -22,6 +22,7 @@
     "//chrome/app/theme:theme_resources_grit",
     "//chrome/browser/ui:browser_element_identifiers",
     "//chrome/browser/ui/color:color_headers",
+    "//components/autofill_ai/core/browser",
     "//components/constrained_window",
     "//components/strings:components_strings_grit",
     "//ui/strings:ui_strings_grit",
diff --git a/chrome/browser/ui/feature_first_run/autofill_ai_first_run_dialog.cc b/chrome/browser/ui/feature_first_run/autofill_ai_first_run_dialog.cc
index 9dec747..80428c22 100644
--- a/chrome/browser/ui/feature_first_run/autofill_ai_first_run_dialog.cc
+++ b/chrome/browser/ui/feature_first_run/autofill_ai_first_run_dialog.cc
@@ -17,6 +17,7 @@
 #include "components/autofill/content/browser/content_autofill_client.h"
 #include "components/autofill/core/browser/foundations/autofill_client.h"
 #include "components/autofill/core/browser/permissions/autofill_ai/autofill_ai_permission_utils.h"
+#include "components/autofill_ai/core/browser/autofill_ai_metrics.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -33,19 +34,21 @@
 #endif
 
 void OnLearnMoreClicked(content::WebContents* web_contents) {
+  autofill_ai::LogOptInFunnelEvent(
+      autofill_ai::AutofillAiOptInFunnelEvents::kFFRLearnMoreButtonClicked);
   Browser* browser = chrome::FindBrowserWithTab(web_contents);
   chrome::ShowSettingsSubPage(browser, chrome::kAutofillAiSubPage);
 }
 
-// TODO(crbug.com/409520456): Record accept button clicks.
 void OnDialogAccepted(content::WebContents* web_contents) {
+  autofill_ai::LogOptInFunnelEvent(
+      autofill_ai::AutofillAiOptInFunnelEvents::kFFRDialogAccepted);
   autofill::AutofillClient* client =
       autofill::ContentAutofillClient::FromWebContents(web_contents);
 
   autofill::SetAutofillAiOptInStatus(*client, true);
 }
 
-// TODO(crbug.com/409520456): Record cancel button clicks.
 void OnDialogCancelled() {
   // Do nothing.
 }
@@ -72,6 +75,8 @@
 }  // namespace
 
 void ShowAutofillAiFirstRunDialog(content::WebContents* web_contents) {
+  autofill_ai::LogOptInFunnelEvent(
+      autofill_ai::AutofillAiOptInFunnelEvents::kFFRDialogShown);
   ShowFeatureFirstRunDialog(
       l10n_util::GetStringUTF16(IDS_AUTOFILL_AI_OPT_IN_IPH_TITLE),
       ui::ImageModel::FromResourceId(IDR_SAVE_PASSPORT),
diff --git a/chrome/browser/ui/hats/hats_service.cc b/chrome/browser/ui/hats/hats_service.cc
index 104c4bd..ea6aec7 100644
--- a/chrome/browser/ui/hats/hats_service.cc
+++ b/chrome/browser/ui/hats/hats_service.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/hats/hats_service.h"
 
+#include "content/public/browser/navigation_handle.h"
+
 HatsService::SurveyMetadata::SurveyMetadata() = default;
 
 HatsService::SurveyMetadata::~SurveyMetadata() = default;
@@ -25,3 +27,24 @@
 hats::SurveyConfigs& HatsService::GetSurveyConfigsByTriggersForTesting() {
   return survey_configs_by_triggers_;
 }
+
+bool HatsService::IsNavigationAllowed(
+    content::NavigationHandle* navigation_handle,
+    HatsService::NavigationBehaviour navigation_behaviour) {
+  if (navigation_behaviour == NavigationBehaviour::ALLOW_ANY ||
+      !navigation_handle || !navigation_handle->IsInPrimaryMainFrame()) {
+    return true;
+  }
+
+  if (navigation_behaviour == NavigationBehaviour::REQUIRE_SAME_DOCUMENT &&
+      navigation_handle->IsSameDocument()) {
+    return true;
+  }
+
+  if (navigation_behaviour == NavigationBehaviour::REQUIRE_SAME_ORIGIN &&
+      navigation_handle->HasCommitted() && navigation_handle->IsSameOrigin()) {
+    return true;
+  }
+
+  return false;
+}
diff --git a/chrome/browser/ui/hats/hats_service.h b/chrome/browser/ui/hats/hats_service.h
index e12c3fd..3f86fbd 100644
--- a/chrome/browser/ui/hats/hats_service.h
+++ b/chrome/browser/ui/hats/hats_service.h
@@ -227,6 +227,12 @@
 
   Profile* profile() const { return profile_; }
 
+  // Checks whether the navigation is allowed under the given navigation
+  // behavior.
+  bool IsNavigationAllowed(
+      content::NavigationHandle* navigation_handle,
+      HatsService::NavigationBehaviour navigation_behaviour);
+
  private:
   friend class DelayedSurveyTask;
   FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
diff --git a/chrome/browser/ui/hats/hats_service_desktop.cc b/chrome/browser/ui/hats/hats_service_desktop.cc
index 17fece2..cb73d20 100644
--- a/chrome/browser/ui/hats/hats_service_desktop.cc
+++ b/chrome/browser/ui/hats/hats_service_desktop.cc
@@ -173,18 +173,8 @@
 
 void HatsServiceDesktop::DelayedSurveyTask::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
-  if (navigation_behaviour_ == NavigationBehaviour::ALLOW_ANY ||
-      !navigation_handle || !navigation_handle->IsInPrimaryMainFrame()) {
-    return;
-  }
-
-  if (navigation_behaviour_ == NavigationBehaviour::REQUIRE_SAME_DOCUMENT &&
-      navigation_handle->IsSameDocument()) {
-    return;
-  }
-
-  if (navigation_behaviour_ == NavigationBehaviour::REQUIRE_SAME_ORIGIN &&
-      navigation_handle->HasCommitted() && navigation_handle->IsSameOrigin()) {
+  if (hats_service_->IsNavigationAllowed(navigation_handle,
+                                         navigation_behaviour_)) {
     return;
   }
 
diff --git a/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.cc b/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.cc
index 55c1a8c..9ef2c046 100644
--- a/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.cc
+++ b/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.cc
@@ -99,6 +99,11 @@
   if (!permission_types.contains(ContentSettingsType::NOTIFICATIONS)) {
     return;
   }
+  base::Value stored_value(hcsm_->GetWebsiteSetting(
+      url, url, ContentSettingsType::REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS));
+  if (stored_value.is_none()) {
+    return;
+  }
   // Set this to true to prevent removal of revoked setting values.
   is_abusive_site_revocation_running_ = true;
   UpdateNotificationPermission(hcsm_.get(), url,
diff --git a/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.cc b/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.cc
index ccb8fd1..08291cb 100644
--- a/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.cc
+++ b/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.cc
@@ -17,6 +17,7 @@
 #include "components/permissions/notifications_engagement_service.h"
 #include "components/safe_browsing/core/common/features.h"
 #include "components/site_engagement/content/site_engagement_service.h"
+#include "safety_hub_constants.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "url/gurl.h"
 
@@ -208,6 +209,12 @@
     return;
   }
 
+  if (*revoked_status == safety_hub::kIgnoreStr) {
+    base::UmaHistogramEnumeration(kRevocationResultHistogram,
+                                  RevocationResult::kIgnore);
+    return;
+  }
+
   if (*revoked_status != safety_hub::kProposedStr) {
     return;
   }
@@ -293,6 +300,93 @@
   return is_revocation_running_;
 }
 
+void DisruptiveNotificationPermissionsManager::RegrantPermissionForUrl(
+    const GURL& url) {
+  // If the user decides to regrant permissions for `url`, check if it has
+  // revoked disruptive notification permissions. If so, allow notification
+  // permissions and ignore the `url` from future auto-revocation.
+  if (!safety_hub_util::IsUrlRevokedDisruptiveNotification(hcsm_.get(), url)) {
+    return;
+  }
+  is_revocation_running_ = true;
+
+  UpdateNotificationPermission(hcsm_.get(), url,
+                               ContentSetting::CONTENT_SETTING_ALLOW);
+  base::Value stored_value(hcsm_->GetWebsiteSetting(
+      url, url,
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS));
+  base::Value::Dict dict = std::move(stored_value).TakeDict();
+  dict.Set(safety_hub::kRevokedStatusDictKeyStr, safety_hub::kIgnoreStr);
+  // Update the stored status value to "ignore" while clearing the constraints
+  // so the value won't expire.
+  UpdateContentSettingValue(hcsm_.get(), url, std::move(dict),
+                            /*constraints*/ {});
+
+  is_revocation_running_ = false;
+}
+
+void DisruptiveNotificationPermissionsManager::UndoRegrantPermissionForUrl(
+    const GURL& url,
+    std::set<ContentSettingsType> permission_types,
+    content_settings::ContentSettingConstraints constraints) {
+  // The user has decided to undo the regranted permission revocation for `url`.
+  // Only update the `NOTIFICATIONS` and
+  // `REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS` settings if the url had
+  // revoked notification permissions.
+  if (!permission_types.contains(ContentSettingsType::NOTIFICATIONS)) {
+    return;
+  }
+  base::Value stored_value(hcsm_->GetWebsiteSetting(
+      url, url,
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS));
+  if (stored_value.is_none()) {
+    return;
+  }
+
+  is_revocation_running_ = true;
+  UpdateNotificationPermission(hcsm_.get(), url,
+                               ContentSetting::CONTENT_SETTING_DEFAULT);
+  base::Value::Dict dict = std::move(stored_value).TakeDict();
+  dict.Set(safety_hub::kRevokedStatusDictKeyStr, safety_hub::kRevokeStr);
+  UpdateContentSettingValue(hcsm_.get(), url, std::move(dict), constraints);
+
+  is_revocation_running_ = false;
+}
+
+void DisruptiveNotificationPermissionsManager::ClearRevokedPermissionsList() {
+  ContentSettingsForOneType revoked_permissions = hcsm_->GetSettingsForOneType(
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS);
+  for (const auto& revoked_permission : revoked_permissions) {
+    const GURL& url = revoked_permission.primary_pattern.ToRepresentativeUrl();
+    base::Value stored_value(hcsm_->GetWebsiteSetting(
+        url, url,
+        ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS));
+    if (stored_value.is_none()) {
+      continue;
+    }
+    CHECK(stored_value.is_dict());
+    const std::string& setting_val =
+        stored_value.GetDict()
+            .Find(safety_hub::kRevokedStatusDictKeyStr)
+            ->GetString();
+    if (setting_val != safety_hub::kRevokeStr) {
+      continue;
+    }
+
+    DeleteRevokedPermissionContentSetting(revoked_permission.primary_pattern,
+                                          revoked_permission.secondary_pattern);
+  }
+}
+
+void DisruptiveNotificationPermissionsManager::
+    DeleteRevokedPermissionContentSetting(
+        const ContentSettingsPattern& primary_pattern,
+        const ContentSettingsPattern& secondary_pattern) {
+  hcsm_->SetWebsiteSettingCustomScope(
+      primary_pattern, secondary_pattern,
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS, {});
+}
+
 bool DisruptiveNotificationPermissionsManager::IsNotificationDisruptive(
     const GURL& url,
     int daily_notification_count) {
diff --git a/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.h b/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.h
index 309ce70..84031f99 100644
--- a/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.h
+++ b/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.h
@@ -41,6 +41,9 @@
   // kRevoke). After the permission is revoked, the content setting is removed
   // so the site won't be reported anymore.
   //
+  // Undo: If the user has undone the revocation, the site is marked as "ignore"
+  // so it won't be revoked on the next runs.
+  //
   // LINT.IfChange(RevocationResult)
   enum class RevocationResult {
     kNotAllowedContentSetting = 0,
@@ -54,7 +57,8 @@
     kNoRevokeDefaultBlock = 8,
     kAlreadyFalsePositive = 9,
     kRevoke = 10,
-    kMaxValue = kRevoke,
+    kIgnore = 11,
+    kMaxValue = kIgnore,
   };
   // LINT.ThenChange(//tools/metrics/histograms/metadata/settings/enums.xml:DisruptiveNotificationRevocationResult)
 
@@ -80,6 +84,30 @@
   // Returns true if settings are being changed due to auto revocation;
   bool IsRevocationRunning();
 
+  // If the url has a revoked disruptive notification permission, this method
+  // allows the notification permissions again and adds a constraint so that
+  // this permission is not auto-revoked during future Safety Hub checks.
+  void RegrantPermissionForUrl(const GURL& url);
+
+  // If `permission_types` includes notifications, undo the actions from
+  // `RegrantPermissionForUrl` by changing the `NOTIFICATIONS` setting back
+  // to `CONTENT_SETTING_ASK` and the
+  // `REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS` status value back to
+  // `safety_hub::kRevokeStr`.
+  void UndoRegrantPermissionForUrl(
+      const GURL& url,
+      std::set<ContentSettingsType> permission_types,
+      content_settings::ContentSettingConstraints constraints);
+
+  // Clear the list of revoked notification permissions so they will no longer
+  // be shown to the user. Does not change permissions themselves.
+  void ClearRevokedPermissionsList();
+
+  // Removes the `REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS` setting.
+  void DeleteRevokedPermissionContentSetting(
+      const ContentSettingsPattern& primary_pattern,
+      const ContentSettingsPattern& secondary_pattern);
+
   // Logs metrics for proposed disruptive notification revocation, to be called
   // when displaying a persistent notification.
   static void LogMetrics(Profile* profile,
diff --git a/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager_unittest.cc b/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager_unittest.cc
index 3580273..e48cbef9d 100644
--- a/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager_unittest.cc
+++ b/chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager_unittest.cc
@@ -475,6 +475,263 @@
                                  0.0);
 }
 
+TEST_F(DisruptiveNotificationPermissionsManagerRevocationTest,
+       RegrantPermission) {
+  base::HistogramTester t;
+  GURL url("https://www.example.com");
+
+  // Set up a revoked disruptive notification.
+  SetDailyAverageNotificationCount(url, 3);
+  site_engagement_service()->ResetBaseScoreForURL(url, 0);
+  EXPECT_EQ(
+      CONTENT_SETTING_ASK,
+      hcsm()->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
+
+  // Set up a revoked content setting.
+  base::Value::Dict dict;
+  dict.Set(safety_hub::kRevokedStatusDictKeyStr, safety_hub::kRevokeStr);
+  dict.Set(safety_hub::kSiteEngagementStr, 0.0);
+  dict.Set(safety_hub::kDailyNotificationCountStr, 3);
+  dict.Set(safety_hub::kTimestampStr, base::TimeToValue(base::Time::Now()));
+
+  content_settings::ContentSettingConstraints constraint(base::Time::Now());
+  constraint.set_lifetime(safety_hub_util::GetCleanUpThreshold());
+
+  hcsm()->SetWebsiteSettingCustomScope(
+      ContentSettingsPattern::FromURLNoWildcard(url),
+      ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS,
+      base::Value(std::move(dict)), constraint);
+
+  manager()->RegrantPermissionForUrl(url);
+  // Notifications are again allowed.
+  EXPECT_EQ(
+      CONTENT_SETTING_ALLOW,
+      hcsm()->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
+
+  // The content setting was updated to "ignore" to prevent autorevoking in the
+  // future.
+  EXPECT_EQ(GetRevokedPermissionsCount(), 1);
+  content_settings::SettingInfo info;
+  base::Value stored_value = hcsm()->GetWebsiteSetting(
+      url, url,
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS, &info);
+  EXPECT_FALSE(stored_value.is_none());
+  ASSERT_TRUE(stored_value.is_dict());
+  EXPECT_EQ(safety_hub::kIgnoreStr,
+            stored_value.GetDict()
+                .Find(safety_hub::kRevokedStatusDictKeyStr)
+                ->GetString());
+  // The constraint was also reset to not expire.
+  EXPECT_TRUE(info.metadata.lifetime().is_zero());
+
+  manager()->RevokeDisruptiveNotifications();
+  // The site is reported as ignored for revocation and not revoked.
+  t.ExpectBucketCount(kRevocationResultHistogram, RevocationResult::kIgnore, 1);
+}
+
+TEST_F(DisruptiveNotificationPermissionsManagerRevocationTest,
+       NoRegrantPermissionMissingContentSetting) {
+  base::HistogramTester t;
+  GURL url("https://www.example.com");
+  EXPECT_EQ(
+      CONTENT_SETTING_ASK,
+      hcsm()->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
+  manager()->RegrantPermissionForUrl(url);
+  // Notifications are still ask.
+  EXPECT_EQ(
+      CONTENT_SETTING_ASK,
+      hcsm()->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
+}
+
+TEST_F(DisruptiveNotificationPermissionsManagerRevocationTest,
+       UndoRegrantPermission) {
+  base::HistogramTester t;
+  GURL url("https://www.example.com");
+
+  // Set up a disruptive notification.
+  SetNotificationPermission(url, CONTENT_SETTING_ALLOW);
+  SetDailyAverageNotificationCount(url, 3);
+  site_engagement_service()->ResetBaseScoreForURL(url, 0);
+
+  // Set up an ignored value.
+  base::Value::Dict dict;
+  dict.Set(safety_hub::kRevokedStatusDictKeyStr, safety_hub::kIgnoreStr);
+  dict.Set(safety_hub::kSiteEngagementStr, 0.0);
+  dict.Set(safety_hub::kDailyNotificationCountStr, 3);
+  dict.Set(safety_hub::kTimestampStr, base::TimeToValue(base::Time::Now()));
+
+  hcsm()->SetWebsiteSettingCustomScope(
+      ContentSettingsPattern::FromURLNoWildcard(url),
+      ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS,
+      base::Value(std::move(dict)));
+
+  // Undo the regrant (return to revoked state).
+  content_settings::ContentSettingConstraints constraint(base::Time::Now());
+  constraint.set_lifetime(safety_hub_util::GetCleanUpThreshold());
+  manager()->UndoRegrantPermissionForUrl(
+      url, {ContentSettingsType::NOTIFICATIONS}, constraint.Clone());
+
+  // Notifications are again ask.
+  EXPECT_EQ(
+      CONTENT_SETTING_ASK,
+      hcsm()->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
+
+  // The content setting was updated to "revoke".
+  EXPECT_EQ(GetRevokedPermissionsCount(), 1);
+  content_settings::SettingInfo info;
+  base::Value stored_value = hcsm()->GetWebsiteSetting(
+      url, url,
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS, &info);
+  EXPECT_FALSE(stored_value.is_none());
+  ASSERT_TRUE(stored_value.is_dict());
+  dict = std::move(stored_value).TakeDict();
+  EXPECT_EQ(safety_hub::kRevokeStr,
+            dict.Find(safety_hub::kRevokedStatusDictKeyStr)->GetString());
+  EXPECT_EQ(0.0, dict.FindDouble(safety_hub::kSiteEngagementStr).value_or(0));
+  EXPECT_EQ(3,
+            dict.FindInt(safety_hub::kDailyNotificationCountStr).value_or(0));
+  const base::Value* stored_timestamp = dict.Find(safety_hub::kTimestampStr);
+  EXPECT_EQ(base::Time::Now(),
+            base::ValueToTime(stored_timestamp).value_or(base::Time()));
+
+  // The constraint was set.
+  EXPECT_EQ(info.metadata.lifetime(), safety_hub_util::GetCleanUpThreshold());
+}
+
+TEST_F(DisruptiveNotificationPermissionsManagerRevocationTest,
+       NoUndoRegrantPermissionMissingContentSetting) {
+  base::HistogramTester t;
+  GURL url("https://www.example.com");
+
+  // Set up a disruptive notification.
+  SetNotificationPermission(url, CONTENT_SETTING_ALLOW);
+  SetDailyAverageNotificationCount(url, 3);
+  site_engagement_service()->ResetBaseScoreForURL(url, 0);
+
+  // Attempt to undo the regrant (return to revoked state).
+  content_settings::ContentSettingConstraints constraint(base::Time::Now());
+  constraint.set_lifetime(safety_hub_util::GetCleanUpThreshold());
+  manager()->UndoRegrantPermissionForUrl(
+      url, {ContentSettingsType::NOTIFICATIONS}, constraint.Clone());
+
+  // Notifications are still allow because there were no "ignore" value stored
+  // therefore no revocation to undo.
+  EXPECT_EQ(
+      CONTENT_SETTING_ALLOW,
+      hcsm()->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
+}
+
+TEST_F(DisruptiveNotificationPermissionsManagerRevocationTest,
+       NoUndoRegrantPermissionWrongContentSettingType) {
+  base::HistogramTester t;
+  GURL url("https://www.example.com");
+
+  // Set up a disruptive notification.
+  SetNotificationPermission(url, CONTENT_SETTING_ALLOW);
+  SetDailyAverageNotificationCount(url, 3);
+  site_engagement_service()->ResetBaseScoreForURL(url, 0);
+
+  // Set up an ignored value.
+  base::Value::Dict dict;
+  dict.Set(safety_hub::kRevokedStatusDictKeyStr, safety_hub::kIgnoreStr);
+  dict.Set(safety_hub::kSiteEngagementStr, 0.0);
+  dict.Set(safety_hub::kDailyNotificationCountStr, 3);
+  dict.Set(safety_hub::kTimestampStr, base::TimeToValue(base::Time::Now()));
+
+  hcsm()->SetWebsiteSettingCustomScope(
+      ContentSettingsPattern::FromURLNoWildcard(url),
+      ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS,
+      base::Value(std::move(dict)));
+
+  // Attempt to undo the regrant (return to revoked state).
+  content_settings::ContentSettingConstraints constraint(base::Time::Now());
+  constraint.set_lifetime(safety_hub_util::GetCleanUpThreshold());
+  manager()->UndoRegrantPermissionForUrl(
+      url, {ContentSettingsType::GEOLOCATION}, constraint.Clone());
+
+  // Notifications are still allow because there were no "ignore" value stored
+  // therefore no revocation to undo.
+  EXPECT_EQ(
+      CONTENT_SETTING_ALLOW,
+      hcsm()->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS));
+}
+
+TEST_F(DisruptiveNotificationPermissionsManagerRevocationTest,
+       ClearRevokedPermissionsList) {
+  base::HistogramTester t;
+
+  // Set up a revoked permission.
+  GURL revoked_url("https://www.example1.com");
+  base::Value::Dict revoked_dict;
+  revoked_dict.Set(safety_hub::kRevokedStatusDictKeyStr,
+                   safety_hub::kRevokeStr);
+  revoked_dict.Set(safety_hub::kSiteEngagementStr, 0.0);
+  revoked_dict.Set(safety_hub::kDailyNotificationCountStr, 3);
+  revoked_dict.Set(safety_hub::kTimestampStr,
+                   base::TimeToValue(base::Time::Now()));
+
+  hcsm()->SetWebsiteSettingCustomScope(
+      ContentSettingsPattern::FromURLNoWildcard(revoked_url),
+      ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS,
+      base::Value(std::move(revoked_dict)));
+
+  // Set up a proposed permission.
+  GURL proposed_url("https://www.example2.com");
+  base::Value::Dict proposed_dict;
+  proposed_dict.Set(safety_hub::kRevokedStatusDictKeyStr,
+                    safety_hub::kProposedStr);
+  proposed_dict.Set(safety_hub::kSiteEngagementStr, 0.0);
+  proposed_dict.Set(safety_hub::kDailyNotificationCountStr, 3);
+  proposed_dict.Set(safety_hub::kTimestampStr,
+                    base::TimeToValue(base::Time::Now()));
+
+  hcsm()->SetWebsiteSettingCustomScope(
+      ContentSettingsPattern::FromURLNoWildcard(proposed_url),
+      ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS,
+      base::Value(std::move(proposed_dict)));
+
+  // Set up an ignored permission.
+  GURL ignored_url("https://www.example3.com");
+  base::Value::Dict ignored_dict;
+  ignored_dict.Set(safety_hub::kRevokedStatusDictKeyStr,
+                   safety_hub::kIgnoreStr);
+  ignored_dict.Set(safety_hub::kSiteEngagementStr, 0.0);
+  ignored_dict.Set(safety_hub::kDailyNotificationCountStr, 3);
+  ignored_dict.Set(safety_hub::kTimestampStr,
+                   base::TimeToValue(base::Time::Now()));
+
+  hcsm()->SetWebsiteSettingCustomScope(
+      ContentSettingsPattern::FromURLNoWildcard(ignored_url),
+      ContentSettingsPattern::Wildcard(),
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS,
+      base::Value(std::move(ignored_dict)));
+
+  EXPECT_EQ(GetRevokedPermissionsCount(), 3);
+  manager()->ClearRevokedPermissionsList();
+  EXPECT_EQ(GetRevokedPermissionsCount(), 2);
+
+  // Only revoked value is cleared, others are not affected.
+  base::Value revoked_stored_value = hcsm()->GetWebsiteSetting(
+      revoked_url, revoked_url,
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS);
+  EXPECT_TRUE(revoked_stored_value.is_none());
+
+  base::Value proposed_stored_value = hcsm()->GetWebsiteSetting(
+      proposed_url, proposed_url,
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS);
+  EXPECT_FALSE(proposed_stored_value.is_none());
+
+  base::Value ignored_stored_value = hcsm()->GetWebsiteSetting(
+      ignored_url, ignored_url,
+      ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS);
+  EXPECT_FALSE(ignored_stored_value.is_none());
+}
+
 class DisruptiveNotificationPermissionsManagerShadowRunTest
     : public DisruptiveNotificationPermissionsManagerTest {
  public:
diff --git a/chrome/browser/ui/safety_hub/revoked_permissions_service.cc b/chrome/browser/ui/safety_hub/revoked_permissions_service.cc
index bfffd52e..5a39c56 100644
--- a/chrome/browser/ui/safety_hub/revoked_permissions_service.cc
+++ b/chrome/browser/ui/safety_hub/revoked_permissions_service.cc
@@ -391,15 +391,6 @@
 #endif
             hcsm());
 
-    if (base::FeatureList::IsEnabled(
-            safe_browsing::kSafetyHubDisruptiveNotificationRevocation)) {
-      disruptive_notification_manager_ =
-          std::make_unique<DisruptiveNotificationPermissionsManager>(
-              hcsm(),
-              site_engagement::SiteEngagementServiceFactory::GetForProfile(
-                  browser_context_));
-    }
-
     pref_change_registrar_->Add(
         prefs::kSafeBrowsingEnabled,
         base::BindRepeating(&RevokedPermissionsService::
@@ -407,6 +398,15 @@
                             base::Unretained(this)));
   }
 
+  if (base::FeatureList::IsEnabled(
+          safe_browsing::kSafetyHubDisruptiveNotificationRevocation)) {
+    disruptive_notification_manager_ =
+        std::make_unique<DisruptiveNotificationPermissionsManager>(
+            hcsm(),
+            site_engagement::SiteEngagementServiceFactory::GetForProfile(
+                browser_context_));
+  }
+
   bool migration_completed = pref_change_registrar_->prefs()->GetBoolean(
       safety_hub_prefs::kUnusedSitePermissionsRevocationMigrationCompleted);
   if (!migration_completed) {
@@ -484,6 +484,10 @@
           ->DeletePatternFromRevokedAbusiveNotificationList(primary_pattern,
                                                             secondary_pattern);
     }
+    if (disruptive_notification_manager_) {
+      disruptive_notification_manager_->DeleteRevokedPermissionContentSetting(
+          primary_pattern, secondary_pattern);
+    }
   }
 }
 
@@ -498,6 +502,10 @@
         origin.GetURL());
   }
 
+  if (disruptive_notification_manager_) {
+    disruptive_notification_manager_->RegrantPermissionForUrl(origin.GetURL());
+  }
+
   content_settings::SettingInfo info;
   base::Value stored_value(hcsm()->GetWebsiteSetting(
       origin.GetURL(), origin.GetURL(),
@@ -574,6 +582,13 @@
         permissions_data.constraints.Clone());
   }
 
+  if (disruptive_notification_manager_) {
+    disruptive_notification_manager_->UndoRegrantPermissionForUrl(
+        GURL(permissions_data.primary_pattern.ToString()),
+        permissions_data.permission_types,
+        permissions_data.constraints.Clone());
+  }
+
   // If `permissions_data` had abusive notifications revoked, remove the
   // `NOTIFICATIONS` setting from the list of permission types to handle below,
   // since these were already handled. If there are no unused site permissions
@@ -617,6 +632,10 @@
     abusive_notification_manager_->ClearRevokedPermissionsList();
   }
 
+  if (disruptive_notification_manager_) {
+    disruptive_notification_manager_->ClearRevokedPermissionsList();
+  }
+
   for (const auto& revoked_permissions : hcsm()->GetSettingsForOneType(
            ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS)) {
     DeletePatternFromRevokedPermissionList(
diff --git a/chrome/browser/ui/safety_hub/revoked_permissions_service_unittest.cc b/chrome/browser/ui/safety_hub/revoked_permissions_service_unittest.cc
index 61ffd2ac..bd10cb5c 100644
--- a/chrome/browser/ui/safety_hub/revoked_permissions_service_unittest.cc
+++ b/chrome/browser/ui/safety_hub/revoked_permissions_service_unittest.cc
@@ -159,7 +159,8 @@
     : public ChromeRenderViewHostTestHarness,
       public testing::WithParamInterface<
           std::tuple</*should_setup_abusive_notification_sites*/ bool,
-                     /*should_setup_unused_sites*/ bool>> {
+                     /*should_setup_unused_sites*/ bool,
+                     /*should_setup_disruptive_sites*/ bool>> {
  public:
   RevokedPermissionsServiceTest() {
     std::vector<base::test::FeatureRef> enabled_features;
@@ -173,8 +174,10 @@
       enabled_features.push_back(
           safe_browsing::kSafetyHubAbusiveNotificationRevocation);
     }
-    enabled_features.push_back(
-        safe_browsing::kSafetyHubDisruptiveNotificationRevocation);
+    if (ShouldSetupDisruptiveSites()) {
+      enabled_features.push_back(
+          safe_browsing::kSafetyHubDisruptiveNotificationRevocation);
+    }
     feature_list_.InitWithFeatures(
         /*enabled_features=*/enabled_features,
         /*disabled_features=*/{});
@@ -223,6 +226,7 @@
 
   bool ShouldSetupAbusiveNotificationSites() { return get<0>(GetParam()); }
   bool ShouldSetupUnusedSites() { return get<1>(GetParam()); }
+  bool ShouldSetupDisruptiveSites() { return get<2>(GetParam()); }
 
   void ResetService() {
     // Setting the factory has the side effect of resetting the service
@@ -319,6 +323,13 @@
     EXPECT_EQ(expected_size, revoked_permissions_list.size());
   }
 
+  void ExpectRevokedDisruptiveNotificationPermissionSize(size_t expected_size) {
+    ContentSettingsForOneType revoked_permissions =
+        hcsm()->GetSettingsForOneType(
+            ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS);
+    EXPECT_EQ(expected_size, revoked_permissions.size());
+  }
+
   void SetupRevokedUnusedPermissionSite(
       std::string url,
       base::TimeDelta lifetime =
@@ -440,6 +451,42 @@
         is_regranted ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_ASK);
   }
 
+  void ExpectRevokedDisruptiveNotificationSettingValues(std::string url) {
+    base::Value stored_value = hcsm()->GetWebsiteSetting(
+        GURL(url), GURL(url),
+        ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS);
+    EXPECT_FALSE(stored_value.is_none());
+    ASSERT_TRUE(stored_value.is_dict());
+    EXPECT_EQ(safety_hub::kRevokeStr,
+              stored_value.GetDict()
+                  .Find(safety_hub::kRevokedStatusDictKeyStr)
+                  ->GetString());
+    EXPECT_TRUE(
+        safety_hub_util::IsUrlRevokedDisruptiveNotification(hcsm(), GURL(url)));
+    EXPECT_EQ(
+        hcsm()->GetContentSetting(GURL(url), GURL(url), notifications_type),
+        CONTENT_SETTING_ASK);
+  }
+
+  void ExpectCleanedUpDisruptiveNotificationSettingValues(
+      std::string url,
+      bool is_regranted = false) {
+    base::Value stored_value = hcsm()->GetWebsiteSetting(
+        GURL(url), GURL(url),
+        ContentSettingsType::REVOKED_DISRUPTIVE_NOTIFICATION_PERMISSIONS);
+    EXPECT_FALSE(stored_value.is_none());
+    ASSERT_TRUE(stored_value.is_dict());
+    EXPECT_NE(safety_hub::kRevokeStr,
+              stored_value.GetDict()
+                  .Find(safety_hub::kRevokedStatusDictKeyStr)
+                  ->GetString());
+    EXPECT_FALSE(
+        safety_hub_util::IsUrlRevokedDisruptiveNotification(hcsm(), GURL(url)));
+    EXPECT_EQ(
+        hcsm()->GetContentSetting(GURL(url), GURL(url), notifications_type),
+        is_regranted ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_ASK);
+  }
+
   void ExpectSafeNotificationSettingValues(std::string url) {
     EXPECT_FALSE(IsUrlInContentSettings(
         safety_hub_util::GetRevokedAbusiveNotificationPermissions(hcsm()),
@@ -849,13 +896,18 @@
   if (ShouldSetupUnusedSites()) {
     SetupRevokedUnusedPermissionSite(url1);
     SetupRevokedUnusedPermissionSite(url2);
-    EXPECT_EQ(2U, GetRevokedUnusedPermissions(hcsm()).size());
+    SetupRevokedUnusedPermissionSite(url5);
+    EXPECT_EQ(3U, GetRevokedUnusedPermissions(hcsm()).size());
+  }
+  if (ShouldSetupDisruptiveSites()) {
+    SetupRevokedDisruptiveNotificationSite(url4);
+    SetupRevokedDisruptiveNotificationSite(url5);
   }
 
   // Allow the permission for `url1` again, which is unused.
   service()->RegrantPermissionsForOrigin(url::Origin::Create(GURL(url1)));
   if (ShouldSetupUnusedSites()) {
-    EXPECT_EQ(1U, GetRevokedUnusedPermissions(hcsm()).size());
+    EXPECT_EQ(2U, GetRevokedUnusedPermissions(hcsm()).size());
     // Check if the permissions of `url1` is regranted.
     EXPECT_EQ(
         ContentSetting::CONTENT_SETTING_ALLOW,
@@ -872,7 +924,7 @@
   // Allow the permission for `url2`, which is both abusive and unused.
   service()->RegrantPermissionsForOrigin(url::Origin::Create(GURL(url2)));
   if (ShouldSetupUnusedSites()) {
-    EXPECT_EQ(0U, GetRevokedUnusedPermissions(hcsm()).size());
+    EXPECT_EQ(1U, GetRevokedUnusedPermissions(hcsm()).size());
     // Check if the permissions of `url2` is regranted.
     EXPECT_EQ(
         ContentSetting::CONTENT_SETTING_ALLOW,
@@ -890,7 +942,7 @@
   // Allow the permission for `url3`, which is abusive.
   service()->RegrantPermissionsForOrigin(url::Origin::Create(GURL(url3)));
   if (ShouldSetupUnusedSites()) {
-    EXPECT_EQ(0U, GetRevokedUnusedPermissions(hcsm()).size());
+    EXPECT_EQ(1U, GetRevokedUnusedPermissions(hcsm()).size());
   }
   if (ShouldSetupAbusiveNotificationSites()) {
     ExpectRevokedAbusiveNotificationPermissionSize(0U);
@@ -900,6 +952,29 @@
                                                     /*is_regranted=*/true);
   }
 
+  // Allow the permission for `url4`, which is disruptive.
+  service()->RegrantPermissionsForOrigin(url::Origin::Create(GURL(url4)));
+  if (ShouldSetupDisruptiveSites()) {
+    ExpectCleanedUpDisruptiveNotificationSettingValues(url4,
+                                                       /*is_regranted=*/true);
+  }
+
+  // Allow the permission for `url5`, which is unused and disruptive.
+  service()->RegrantPermissionsForOrigin(url::Origin::Create(GURL(url5)));
+  if (ShouldSetupUnusedSites()) {
+    EXPECT_EQ(0U, GetRevokedUnusedPermissions(hcsm()).size());
+    // Check if the permissions of `url5` is regranted.
+    EXPECT_EQ(
+        ContentSetting::CONTENT_SETTING_ALLOW,
+        hcsm()->GetContentSetting(GURL(url1), GURL(url5), geolocation_type));
+    EXPECT_EQ(base::Value::Dict().Set("foo", "bar"),
+              hcsm()->GetWebsiteSetting(GURL(url1), GURL(url5), chooser_type));
+  }
+  if (ShouldSetupDisruptiveSites()) {
+    ExpectCleanedUpDisruptiveNotificationSettingValues(url5,
+                                                       /*is_regranted=*/true);
+  }
+
   // Undoing the changes should add `url1` back to the list of revoked
   // permissions and reset its permissions.
   UndoRegrantPermissionsForUrl(url1, unused_permission_types);
@@ -952,6 +1027,28 @@
     ExpectRevokedAbusiveNotificationSettingValues(url2);
     ExpectRevokedAbusiveNotificationSettingValues(url3);
   }
+
+  // Undoing `url4` adds it back to the revoked disruptive notification
+  // permissions list.
+  UndoRegrantPermissionsForUrl(url4, {notifications_type});
+  if (ShouldSetupDisruptiveSites()) {
+    ExpectRevokedDisruptiveNotificationSettingValues(url4);
+  }
+
+  // Undoing `url5` adds it back to the revoked permissions lists.
+  UndoRegrantPermissionsForUrl(
+      url5, {notifications_type, geolocation_type, chooser_type});
+  if (ShouldSetupUnusedSites()) {
+    EXPECT_EQ(3U, GetRevokedUnusedPermissions(hcsm()).size());
+    EXPECT_EQ(
+        ContentSetting::CONTENT_SETTING_ASK,
+        hcsm()->GetContentSetting(GURL(url5), GURL(url5), geolocation_type));
+    EXPECT_EQ(base::Value(),
+              hcsm()->GetWebsiteSetting(GURL(url5), GURL(url5), chooser_type));
+  }
+  if (ShouldSetupDisruptiveSites()) {
+    ExpectRevokedDisruptiveNotificationSettingValues(url5);
+  }
 }
 
 TEST_P(RevokedPermissionsServiceTest, RegrantPreventsAutorevoke) {
@@ -1095,12 +1192,16 @@
     SetupRevokedUnusedPermissionSite(url2);
     EXPECT_EQ(2U, GetRevokedUnusedPermissions(hcsm()).size());
   }
+  if (ShouldSetupDisruptiveSites()) {
+    SetupRevokedDisruptiveNotificationSite(url4);
+  }
 
   // Revoked permissions list should be empty after clearing the revoked
   // permissions list.
   service()->ClearRevokedPermissionsList();
   EXPECT_EQ(0U, GetRevokedUnusedPermissions(hcsm()).size());
   ExpectRevokedAbusiveNotificationPermissionSize(0U);
+  ExpectRevokedDisruptiveNotificationPermissionSize(0U);
 }
 
 TEST_P(RevokedPermissionsServiceTest, RecordRegrantMetricForAllowAgain) {
@@ -1210,9 +1311,10 @@
     SetupRevokedUnusedPermissionSite(url5, longer_lifetime);
     SetupRevokedUnusedPermissionSite(url6, shorter_lifetime);
   }
-
-  SetupRevokedDisruptiveNotificationSite(url5, shorter_lifetime);
-  SetupRevokedDisruptiveNotificationSite(url6, longer_lifetime);
+  if (ShouldSetupDisruptiveSites()) {
+    SetupRevokedDisruptiveNotificationSite(url5, shorter_lifetime);
+    SetupRevokedDisruptiveNotificationSite(url6, longer_lifetime);
+  }
 
   // When we start up a new service instance, the latest result (i.e. the list
   // of revoked permissions) should be immediately available.
@@ -1225,46 +1327,84 @@
       static_cast<RevokedPermissionsService::RevokedPermissionsResult*>(
           opt_result.value().get());
   auto revoked_permissions = result->GetRevokedPermissions();
-  if (ShouldSetupUnusedSites() && ShouldSetupAbusiveNotificationSites()) {
-    EXPECT_EQ(6U, revoked_permissions.size());
-    // Verify the constraints are merged properly when there are multiple
-    // revocation types.
-    auto permission_1 = GetPermissionsDataByUrl(revoked_permissions, url1);
-    EXPECT_EQ(permission_1.constraints.lifetime(), default_lifetime);
+  if (ShouldSetupDisruptiveSites()) {
+    if (ShouldSetupUnusedSites() && ShouldSetupAbusiveNotificationSites()) {
+      EXPECT_EQ(6U, revoked_permissions.size());
+      // Verify the constraints are merged properly when there are multiple
+      // revocation types.
+      auto permission_1 = GetPermissionsDataByUrl(revoked_permissions, url1);
+      EXPECT_EQ(permission_1.constraints.lifetime(), default_lifetime);
 
-    auto permission_2 = GetPermissionsDataByUrl(revoked_permissions, url2);
-    EXPECT_EQ(permission_2.constraints.lifetime(), longer_lifetime);
+      auto permission_2 = GetPermissionsDataByUrl(revoked_permissions, url2);
+      EXPECT_EQ(permission_2.constraints.lifetime(), longer_lifetime);
 
-    auto permission_3 = GetPermissionsDataByUrl(revoked_permissions, url3);
-    EXPECT_EQ(permission_3.constraints.lifetime(), default_lifetime);
+      auto permission_3 = GetPermissionsDataByUrl(revoked_permissions, url3);
+      EXPECT_EQ(permission_3.constraints.lifetime(), default_lifetime);
 
-    auto permission_4 = GetPermissionsDataByUrl(revoked_permissions, url4);
-    EXPECT_EQ(permission_4.constraints.lifetime(), longer_lifetime);
+      auto permission_4 = GetPermissionsDataByUrl(revoked_permissions, url4);
+      EXPECT_EQ(permission_4.constraints.lifetime(), longer_lifetime);
 
-    auto permission_5 = GetPermissionsDataByUrl(revoked_permissions, url5);
-    EXPECT_EQ(permission_5.constraints.lifetime(), longer_lifetime);
+      auto permission_5 = GetPermissionsDataByUrl(revoked_permissions, url5);
+      EXPECT_EQ(permission_5.constraints.lifetime(), longer_lifetime);
 
-    auto permission_6 = GetPermissionsDataByUrl(revoked_permissions, url6);
-    EXPECT_EQ(permission_6.constraints.lifetime(), longer_lifetime);
-  } else if (ShouldSetupUnusedSites()) {
-    EXPECT_EQ(5U, revoked_permissions.size());
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url1));
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url2));
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url4));
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url5));
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url6));
-  } else if (ShouldSetupAbusiveNotificationSites()) {
-    EXPECT_EQ(5U, revoked_permissions.size());
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url2));
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url3));
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url4));
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url5));
-    EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url6));
+      auto permission_6 = GetPermissionsDataByUrl(revoked_permissions, url6);
+      EXPECT_EQ(permission_6.constraints.lifetime(), longer_lifetime);
+    } else if (ShouldSetupUnusedSites()) {
+      EXPECT_EQ(5U, revoked_permissions.size());
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url1));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url2));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url4));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url5));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url6));
+    } else if (ShouldSetupAbusiveNotificationSites()) {
+      EXPECT_EQ(5U, revoked_permissions.size());
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url2));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url3));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url4));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url5));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url6));
+    }
+  } else {
+    if (ShouldSetupUnusedSites() && ShouldSetupAbusiveNotificationSites()) {
+      EXPECT_EQ(6U, revoked_permissions.size());
+      // Verify the constraints are merged properly when there are multiple
+      // revocation types.
+      auto permission_1 = GetPermissionsDataByUrl(revoked_permissions, url1);
+      EXPECT_EQ(permission_1.constraints.lifetime(), default_lifetime);
+
+      auto permission_2 = GetPermissionsDataByUrl(revoked_permissions, url2);
+      EXPECT_EQ(permission_2.constraints.lifetime(), longer_lifetime);
+
+      auto permission_3 = GetPermissionsDataByUrl(revoked_permissions, url3);
+      EXPECT_EQ(permission_3.constraints.lifetime(), default_lifetime);
+
+      auto permission_4 = GetPermissionsDataByUrl(revoked_permissions, url4);
+      EXPECT_EQ(permission_4.constraints.lifetime(), longer_lifetime);
+
+      auto permission_5 = GetPermissionsDataByUrl(revoked_permissions, url5);
+      EXPECT_EQ(permission_5.constraints.lifetime(), longer_lifetime);
+
+      auto permission_6 = GetPermissionsDataByUrl(revoked_permissions, url6);
+      EXPECT_EQ(permission_6.constraints.lifetime(), shorter_lifetime);
+    } else if (ShouldSetupUnusedSites()) {
+      EXPECT_EQ(5U, revoked_permissions.size());
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url1));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url2));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url4));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url5));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url6));
+    } else if (ShouldSetupAbusiveNotificationSites()) {
+      EXPECT_EQ(3U, revoked_permissions.size());
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url2));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url3));
+      EXPECT_TRUE(IsUrlInRevokedSettings(revoked_permissions, url4));
+    }
   }
 }
 
 TEST_P(RevokedPermissionsServiceTest, PermissionsRevocationType) {
-  if (!ShouldSetupAbusiveNotificationSites() || !ShouldSetupUnusedSites()) {
+  if (!ShouldSetupAbusiveNotificationSites() || !ShouldSetupUnusedSites() ||
+      !ShouldSetupDisruptiveSites()) {
     return;
   }
 
@@ -1728,7 +1868,8 @@
     RevokedPermissionsServiceTest,
     testing::Combine(
         /*should_setup_abusive_notification_sites=*/testing::Bool(),
-        /*should_setup_unused_sites=*/testing::Bool()));
+        /*should_setup_unused_sites=*/testing::Bool(),
+        /*should_setup_disruptive_sites=*/testing::Bool()));
 
 class RevokedPermissionsServiceStartUpTest
     : public ChromeRenderViewHostTestHarness {
diff --git a/chrome/browser/ui/views/autofill/payments/card_unmask_otp_input_dialog_browsertest.cc b/chrome/browser/ui/views/autofill/payments/card_unmask_otp_input_dialog_browsertest.cc
index 07eacbc..6aaecc6 100644
--- a/chrome/browser/ui/views/autofill/payments/card_unmask_otp_input_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/card_unmask_otp_input_dialog_browsertest.cc
@@ -21,7 +21,8 @@
 
 class CardUnmaskOtpInputDialogBrowserTest
     : public DialogBrowserTest,
-      public testing::WithParamInterface<CardUnmaskChallengeOptionType> {
+      public testing::WithParamInterface<
+          std::tuple<CardUnmaskChallengeOptionType, CreditCard::RecordType>> {
  public:
   CardUnmaskOtpInputDialogBrowserTest() = default;
   CardUnmaskOtpInputDialogBrowserTest(
@@ -33,9 +34,10 @@
   void ShowUi(const std::string& name) override {
     CardUnmaskChallengeOption challenge_option;
     challenge_option.challenge_input_length = kDefaultOtpLength;
-    challenge_option.type = GetParam();
+    challenge_option.type = std::get<0>(GetParam());
+    CreditCard::RecordType card_type = std::get<1>(GetParam());
     controller_ = std::make_unique<CardUnmaskOtpInputDialogControllerImpl>(
-        challenge_option, /*delegate=*/nullptr);
+        card_type, challenge_option, /*delegate=*/nullptr);
     controller_->ShowDialog(base::BindOnce(
         &CreateAndShowOtpInputDialog, controller_->GetWeakPtr(),
         base::Unretained(
@@ -57,7 +59,28 @@
   }
 
   std::string GetOtpAuthType() {
-    return autofill_metrics::GetOtpAuthType(GetParam());
+    switch (std::get<0>(GetParam())) {
+      case CardUnmaskChallengeOptionType::kSmsOtp:
+        return "SmsOtp";
+      case CardUnmaskChallengeOptionType::kEmailOtp:
+        return "EmailOtp";
+      default:
+        NOTREACHED();
+    }
+  }
+
+  std::string GetCardType() {
+    switch (std::get<1>(GetParam())) {
+      case CreditCard::RecordType::kVirtualCard:
+        return "VirtualCard";
+      case CreditCard::RecordType::kFullServerCard:
+      case CreditCard::RecordType::kMaskedServerCard:
+        return "ServerCard";
+      case CreditCard::RecordType::kLocalCard:
+        return "LocalCard";
+      default:
+        NOTREACHED();
+    }
   }
 
  private:
@@ -75,7 +98,9 @@
   // Right now the view is created but not injected. Need to change this when
   // moving this logging.
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() + ".Shown", true, 1);
+      base::StrCat({"Autofill.OtpInputDialog.", GetCardType(), ".",
+                    GetOtpAuthType(), ".Shown"}),
+      true, 1);
 }
 
 // Ensures closing tab while dialog being visible is correctly handled.
@@ -125,8 +150,14 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     CardUnmaskOtpInputDialogBrowserTest,
-    testing::Values(CardUnmaskChallengeOptionType::kSmsOtp,
-                    CardUnmaskChallengeOptionType::kEmailOtp));
+    testing::ValuesIn({
+        std::make_tuple(CardUnmaskChallengeOptionType::kSmsOtp,
+                        CreditCard::RecordType::kVirtualCard),
+        std::make_tuple(CardUnmaskChallengeOptionType::kSmsOtp,
+                        CreditCard::RecordType::kMaskedServerCard),
+        std::make_tuple(CardUnmaskChallengeOptionType::kEmailOtp,
+                        CreditCard::RecordType::kVirtualCard),
+    }));
 
 }  // namespace
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/privacy_sandbox/BUILD.gn b/chrome/browser/ui/views/privacy_sandbox/BUILD.gn
new file mode 100644
index 0000000..c46e0cce
--- /dev/null
+++ b/chrome/browser/ui/views/privacy_sandbox/BUILD.gn
@@ -0,0 +1,13 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+assert(!is_android)
+
+source_set("privacy_sandbox") {
+  public = [ "dialog_origin_marker.h" ]
+
+  sources = [ "dialog_origin_marker.cc" ]
+
+  deps = [ "//content/public/browser" ]
+}
diff --git a/chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.cc b/chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.cc
new file mode 100644
index 0000000..9fecd35a
--- /dev/null
+++ b/chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.cc
@@ -0,0 +1,18 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.h"
+
+#include "content/public/browser/web_contents.h"
+
+namespace privacy_sandbox {
+
+DialogOriginMarker::DialogOriginMarker(content::WebContents* contents)
+    : content::WebContentsUserData<DialogOriginMarker>(*contents) {}
+
+DialogOriginMarker::~DialogOriginMarker() = default;
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(DialogOriginMarker);
+
+}  // namespace privacy_sandbox
diff --git a/chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.h b/chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.h
new file mode 100644
index 0000000..fe2aa70
--- /dev/null
+++ b/chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.h
@@ -0,0 +1,30 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_PRIVACY_SANDBOX_DIALOG_ORIGIN_MARKER_H_
+#define CHROME_BROWSER_UI_VIEWS_PRIVACY_SANDBOX_DIALOG_ORIGIN_MARKER_H_
+
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace privacy_sandbox {
+
+// A marker class attached via WebContentsUserData to WebContents specifically
+// created for the Privacy Sandbox dialog view. This allows Privacy Sandbox
+// WebUI controllers to differentiate between WebContents hosted within the
+// dialog and those loaded in a standard browser tab.
+class DialogOriginMarker
+    : public content::WebContentsUserData<DialogOriginMarker> {
+ public:
+  ~DialogOriginMarker() override;
+
+ private:
+  explicit DialogOriginMarker(content::WebContents* contents);
+
+  friend class content::WebContentsUserData<DialogOriginMarker>;
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
+}  // namespace privacy_sandbox
+
+#endif  // CHROME_BROWSER_UI_VIEWS_PRIVACY_SANDBOX_DIALOG_ORIGIN_MARKER_H_
diff --git a/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view.cc b/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view.cc
index 1700273..fec40f3 100644
--- a/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view.cc
+++ b/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/ui/views/frame/app_menu_button.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
+#include "chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.h"
 #include "chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_dialog_ui.h"
 #include "chrome/common/webui_url_constants.h"
 #include "components/constrained_window/constrained_window_views.h"
@@ -123,6 +124,10 @@
     BrowserWindowInterface* browser)
     : web_view_(AddChildView(std::make_unique<WebView>(browser->GetProfile()))),
       browser_(browser) {
+  // Attach the marker to identify that this WebContents instance originates
+  // from the dialog view, allowing WebUI controllers to tailor behavior.
+  privacy_sandbox::DialogOriginMarker::CreateForWebContents(
+      web_view_->GetWebContents());
   // Override the default zoom level for the Privacy Sandbox dialog. Its size
   // should align with native UI elements, rather than web content.
   auto* web_contents = web_view_->GetWebContents();
diff --git a/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view.h b/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view.h
index 98842b3..3bcb657f 100644
--- a/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view.h
+++ b/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view.h
@@ -32,6 +32,7 @@
   // TODO(chrstne): Create initialization method for PSNotice, v2.
 
   void Close();
+  content::WebContents* GetWebContentsForTesting();
 
  private:
   friend class PrivacySandboxQueueTestNotice;
@@ -45,11 +46,6 @@
   void ShowNativeView();
   void OpenPrivacySandboxSettings();
   void OpenPrivacySandboxAdMeasurementSettings();
-  friend class PrivacySandboxDialogViewPrivacyPolicyBrowserTest;
-  friend class
-      PrivacySandboxDialogViewAdsApiUxEnhancementPrivacyPolicyBrowserTest;
-  friend class PrivacySandboxDialogViewAdsApiUxEnhancementsLearnMoreBrowserTest;
-  content::WebContents* GetWebContentsForTesting();
 
   raw_ptr<views::WebView> web_view_;
   raw_ptr<BrowserWindowInterface> browser_;
diff --git a/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view_browsertest.cc b/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view_browsertest.cc
index 13fb65d..0a7f28bb 100644
--- a/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view_browsertest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.h"
 #include "components/privacy_sandbox/privacy_sandbox_features.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
@@ -155,7 +156,14 @@
         views::test::AnyWidgetTestPasskey{},
         PrivacySandboxDialogView::kViewClassName);
     PrivacySandboxDialog::Show(browser(), prompt_type);
-    waiter.WaitIfNeededAndGet();
+    views::Widget* dialog_widget = waiter.WaitIfNeededAndGet();
+    auto* privacy_sandbox_dialog_view = static_cast<PrivacySandboxDialogView*>(
+        dialog_widget->widget_delegate()->GetContentsView());
+    // Verify that the DialogOriginMarker is present for WebContents created
+    // within the dialog view context.
+    ASSERT_NE(privacy_sandbox::DialogOriginMarker::FromWebContents(
+                  privacy_sandbox_dialog_view->GetWebContentsForTesting()),
+              nullptr);
   }
 
   MockPrivacySandboxService* mock_service() { return mock_service_; }
@@ -306,6 +314,11 @@
 
     auto* privacy_sandbox_dialog_view = static_cast<PrivacySandboxDialogView*>(
         dialog_widget->widget_delegate()->GetContentsView());
+    // Verify that the DialogOriginMarker is present for WebContents created
+    // within the dialog view context.
+    ASSERT_NE(privacy_sandbox::DialogOriginMarker::FromWebContents(
+                  privacy_sandbox_dialog_view->GetWebContentsForTesting()),
+              nullptr);
 
     // Click expand button.
     EXPECT_TRUE(
@@ -402,6 +415,11 @@
 
     auto* privacy_sandbox_dialog_view = static_cast<PrivacySandboxDialogView*>(
         dialog_widget->widget_delegate()->GetContentsView());
+    // Verify that the DialogOriginMarker is present for WebContents created
+    // within the dialog view context.
+    ASSERT_NE(privacy_sandbox::DialogOriginMarker::FromWebContents(
+                  privacy_sandbox_dialog_view->GetWebContentsForTesting()),
+              nullptr);
 
     auto [primary_selector, secondary_selector] =
         GetDialogElementSelector(name);
diff --git a/chrome/browser/ui/webui/management/management_ui_handler_chromeos.cc b/chrome/browser/ui/webui/management/management_ui_handler_chromeos.cc
index c55308b..f4887b9 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler_chromeos.cc
+++ b/chrome/browser/ui/webui/management/management_ui_handler_chromeos.cc
@@ -486,12 +486,10 @@
           &ManagementUIHandlerChromeOS::HandleGetFilesUploadToCloudInfo,
           base::Unretained(this)));
 
-  capture_policy::CheckGetAllScreensMediaAllowedForAnyOrigin(
-      Profile::FromWebUI(web_ui()),
-      base::BindOnce(
-          &ManagementUIHandlerChromeOS::
-              CheckGetAllScreensMediaAllowedForAnyOriginResultReceived,
-          weak_factory_.GetWeakPtr()));
+  capture_policy::CheckGetAllScreensMediaAllowedForAnyOrigin(base::BindOnce(
+      &ManagementUIHandlerChromeOS::
+          CheckGetAllScreensMediaAllowedForAnyOriginResultReceived,
+      weak_factory_.GetWeakPtr()));
 }
 
 // static
diff --git a/chrome/browser/ui/webui/privacy_sandbox/BUILD.gn b/chrome/browser/ui/webui/privacy_sandbox/BUILD.gn
index 9db91b0..d3bd23e0 100644
--- a/chrome/browser/ui/webui/privacy_sandbox/BUILD.gn
+++ b/chrome/browser/ui/webui/privacy_sandbox/BUILD.gn
@@ -43,6 +43,7 @@
       ":privacy_sandbox",
       "//chrome/browser/ui:ui",
       "//chrome/browser/ui/tabs:tab_strip",
+      "//chrome/browser/ui/views/privacy_sandbox",
       "//chrome/test:test_support_ui",
       "//content/test:test_support",
       "//testing/gmock",
diff --git a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_browsertest.cc b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_browsertest.cc
index 6f5e67e..4707e9e 100644
--- a/chrome/browser/ui/webui/privacy_sandbox/base_dialog_browsertest.cc
+++ b/chrome/browser/ui/webui/privacy_sandbox/base_dialog_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/views/privacy_sandbox/dialog_origin_marker.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -19,6 +20,10 @@
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), kUrl));
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
+  // Verify the marker is absent when the URL is loaded directly in a tab, as
+  // opposed to being instantiated via the dialog view.
+  EXPECT_EQ(privacy_sandbox::DialogOriginMarker::FromWebContents(web_contents),
+            nullptr);
   ASSERT_TRUE(web_contents);
   EXPECT_EQ(web_contents->GetLastCommittedURL(), kUrl);
   EXPECT_FALSE(web_contents->IsCrashed());
diff --git a/chrome/browser/ui/webui/signin/signout_confirmation/signout_confirmation_handler.cc b/chrome/browser/ui/webui/signin/signout_confirmation/signout_confirmation_handler.cc
index 3a8339fd..50651b89 100644
--- a/chrome/browser/ui/webui/signin/signout_confirmation/signout_confirmation_handler.cc
+++ b/chrome/browser/ui/webui/signin/signout_confirmation/signout_confirmation_handler.cc
@@ -57,8 +57,7 @@
     case ChromeSignoutConfirmationPromptVariant::kUnsyncedData:
       return IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_UNSYNCED_BODY;
     case ChromeSignoutConfirmationPromptVariant::kUnsyncedDataWithReauthButton:
-      // TODO(crbug.com/407942423): Add a better string for this case.
-      return IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_UNSYNCED_BODY;
+      return IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_VERIFY_BODY;
     case ChromeSignoutConfirmationPromptVariant::kProfileWithParentalControls:
       return IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_KIDS_BODY;
     default:
@@ -73,7 +72,7 @@
     case ChromeSignoutConfirmationPromptVariant::kUnsyncedData:
       return IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_DELETE_AND_SIGNOUT_BUTTON;
     case ChromeSignoutConfirmationPromptVariant::kUnsyncedDataWithReauthButton:
-      return IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_SIGNOUT_BUTTON;
+      return IDS_CHROME_SIGNOUT_CONFIRMATION_PROMPT_DELETE_AND_SIGNOUT_BUTTON;
     case ChromeSignoutConfirmationPromptVariant::kProfileWithParentalControls:
       return IDS_SCREEN_LOCK_SIGN_OUT;
     default:
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 5076a73..cb8eeef 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1745560604-dd8f098dbe0c732dbe20a01bd15db1ba2dfe45c3-035d05bd372ccf966bb8d6178c5bdaa14da93498.profdata
+chrome-android32-main-1745582325-287da6a2c3c6b90e780426395cce81f6afa5e0ca-a0039c24ffa93e9bc6b9966fd076450d79b52310.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index 9b68c89..9edb5791 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1745560604-7a13dc5def3dae7b4496fa1ecbf33fbb43c63360-035d05bd372ccf966bb8d6178c5bdaa14da93498.profdata
+chrome-android64-main-1745582732-a1d0556dab10db0d3325b42a753ba1ef9ffd00aa-81a47570f4fd5b425edaaf5f48af4a4d1b76e639.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index a677d730..3882f6b 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1745539166-575ae93e66f0ca1402d665e33e986ff2b9a9e89b-685559fead00f88eb29e163580aa82068408626d.profdata
+chrome-linux-main-1745560604-81bca531ca25fd1605d5ee6f34db51b94d960b2e-035d05bd372ccf966bb8d6178c5bdaa14da93498.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 91b3401f..4828ecfde 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1745560604-dd618542042b2ae6af2ca93698e0e8430ca1d7d3-035d05bd372ccf966bb8d6178c5bdaa14da93498.profdata
+chrome-mac-arm-main-1745582325-f93f85959935d6d5a59f77c94b2fca294965f533-a0039c24ffa93e9bc6b9966fd076450d79b52310.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index 1c73b538..d310afbc 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1745560604-c9f3dfedbdb3047a3d82b2afb92d3acb81173cbf-035d05bd372ccf966bb8d6178c5bdaa14da93498.profdata
+chrome-win-arm64-main-1745582325-466eb0bc55dde8b154720e6049e44c1815d41215-a0039c24ffa93e9bc6b9966fd076450d79b52310.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index d6cbd27c..19ca2964 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1745506685-96e841da5dd76a7f4a6ba79beb8d8a29f809f22f-c8499c9c087f81fe72fff3472f3e7327aff0e360.profdata
+chrome-win64-main-1745549882-105a137f86f5aa2cfb66de130ef3928759f95460-bcc2d0f3e1e40eaee211f8ff04d0cd4293750f14.profdata
diff --git a/chrome/release_scripts b/chrome/release_scripts
index e4354ac..6e0c25f 160000
--- a/chrome/release_scripts
+++ b/chrome/release_scripts
@@ -1 +1 @@
-Subproject commit e4354acf6ce7f32ee869ddb5dd2cabd6ca2f8b2f
+Subproject commit 6e0c25fb2cf1d16a46c03138d9745536a185e93e
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index a3970ba..c510bdd 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1663,6 +1663,7 @@
       "../browser/ssl/chrome_security_state_client_browsertest.cc",
       "../browser/trusted_vault/trusted_vault_encryption_keys_tab_helper_browsertest.cc",
       "../browser/ui/android/autofill/save_update_address_profile_flow_manager_browsertest.cc",
+      "../browser/ui/android/hats/hats_service_android_browsertest.cc",
       "../browser/ui/android/hats/survey_client_android_browsertest.cc",
       "../browser/ui/android/plus_addresses/all_plus_addresses_bottom_sheet_view_browsertest.cc",
       "../browser/ui/android/plus_addresses/plus_address_creation_view_android_browsertest.cc",
@@ -2313,6 +2314,7 @@
       "//chrome/browser/ui/views/bubble",
       "//chrome/browser/ui/views/download",
       "//chrome/browser/ui/views/page_action:page_action",
+      "//chrome/browser/ui/views/privacy_sandbox",
       "//chrome/browser/ui/views/side_panel",
       "//chrome/browser/ui/views/side_panel:side_panel_enums",
       "//chrome/browser/ui/views/toolbar",
diff --git a/chrome/test/data/webui/settings/autofill_section_test.ts b/chrome/test/data/webui/settings/autofill_section_test.ts
index f5ea16a..98c027d 100644
--- a/chrome/test/data/webui/settings/autofill_section_test.ts
+++ b/chrome/test/data/webui/settings/autofill_section_test.ts
@@ -1183,6 +1183,79 @@
     });
   });
 
+  // Testing address edit dialog in an RTL layout by setting the document
+  // direction to 'rtl'. The phone number input should have direction=ltr and
+  // text-align=end.
+  test('verifyEditingILAddressWithRtlLayout', function() {
+    document.documentElement.dir = 'rtl';
+    const address = createEmptyAddressEntry();
+    address.fields = [
+      {type: FieldType.ADDRESS_HOME_COUNTRY, value: 'IL'},
+      {type: FieldType.NAME_FULL, value: 'Name'},
+      {type: FieldType.ADDRESS_HOME_CITY, value: 'City'},
+      {type: FieldType.ADDRESS_HOME_ZIP, value: 'Postal code'},
+      {type: FieldType.PHONE_HOME_WHOLE_NUMBER, value: 'Phone'},
+      {type: FieldType.EMAIL_ADDRESS, value: 'Email'},
+    ];
+
+    countryDetailManager.setGetAddressFormatRepsonse(ADDRESS_COMPONENTS_IL);
+    return createAddressDialog(address).then(function(dialog) {
+      const rows = dialog.$.dialog.querySelectorAll('.address-row');
+      // There should be 4 rows: Country, Name, City + Zip, Phone + Email
+      assertEquals(4, rows.length);
+
+      let index = 0;
+      // Country
+      let row = rows[index]!;
+      const countrySelect = row.querySelector('select');
+      assertTrue(!!countrySelect);
+      assertEquals(
+          'Israel', countrySelect.selectedOptions[0]!.textContent!.trim());
+      index++;
+      // Name
+      row = rows[index]!;
+      let cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
+          '.address-column');
+      assertEquals(1, cols.length);
+      assertEquals(
+          getAddressFieldValue(address, FieldType.NAME_FULL)!, cols[0]!.value);
+      index++;
+      // City, Postal code
+      row = rows[index]!;
+      cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
+          '.address-column');
+      assertEquals(2, cols.length);
+      assertEquals(
+          getAddressFieldValue(address, FieldType.ADDRESS_HOME_CITY),
+          cols[0]!.value);
+      assertEquals(
+          getAddressFieldValue(address, FieldType.ADDRESS_HOME_ZIP),
+          cols[1]!.value);
+      index++;
+      // Phone, Email
+      row = rows[index]!;
+      cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
+          '.address-column');
+      assertEquals(2, cols.length);
+      assertEquals(
+          getAddressFieldValue(address, FieldType.PHONE_HOME_WHOLE_NUMBER),
+          cols[0]!.value);
+      assertTrue(cols[0]!.classList.contains('phone-number-input'));
+      const phoneInput = (cols[0]! as CrInputElement).inputElement;
+      assertEquals(
+          'ltr',
+          (phoneInput.computedStyleMap().get('direction') as CSSUnitValue)
+              .value);
+      assertEquals(
+          'end',
+          (phoneInput.computedStyleMap().get('text-align') as CSSUnitValue)
+              .value);
+      assertEquals(
+          getAddressFieldValue(address, FieldType.EMAIL_ADDRESS),
+          cols[1]!.value);
+    });
+  });
+
   // US has an extra field 'State'. Validate that this field is
   // persisted when switching to IL then back to US.
   test('verifyAddressPersistanceWhenSwitchingCountries', function() {
diff --git a/clank b/clank
index ab79cee..86860c4 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit ab79cee2b3c5a6548430c71adddeba5e02a6ddc4
+Subproject commit 86860c4de44765ed2d195926b9d3dd6ed1d20efd
diff --git a/components/attribution_reporting/parsing_utils.cc b/components/attribution_reporting/parsing_utils.cc
index b1ef942..0809508 100644
--- a/components/attribution_reporting/parsing_utils.cc
+++ b/components/attribution_reporting/parsing_utils.cc
@@ -19,6 +19,7 @@
 #include "base/check.h"
 #include "base/containers/flat_set.h"
 #include "base/containers/flat_tree.h"
+#include "base/containers/to_vector.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/abseil_string_number_conversions.h"
 #include "base/strings/string_number_conversions.h"
@@ -321,14 +322,10 @@
     return base::unexpected(StringSetError::kSetTooLong);
   }
 
-  std::vector<std::string> values;
-  values.reserve(list.size());
-
-  for (base::Value& item : list) {
-    values.emplace_back(std::move(item).TakeString());
-  }
-
-  return base::flat_set<std::string>(base::sorted_unique, std::move(values));
+  return base::flat_set<std::string>(
+      base::sorted_unique, base::ToVector(list, [](base::Value& item) {
+        return std::move(item).TakeString();
+      }));
 }
 
 }  // namespace attribution_reporting
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index bbf9ed21..2ac0660 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -1108,6 +1108,8 @@
     "test_utils/valuables_data_test_utils.cc",
     "test_utils/valuables_data_test_utils.h",
     "test_utils/vote_uploads_test_matchers.h",
+    "ui/mock_autofill_image_fetcher.cc",
+    "ui/mock_autofill_image_fetcher.h",
     "ui/mock_autofill_suggestion_delegate.cc",
     "ui/mock_autofill_suggestion_delegate.h",
     "ui/payments/virtual_card_enroll_ui_model_test_api.h",
diff --git a/components/autofill/core/browser/data_manager/payments/payments_data_manager_unittest.cc b/components/autofill/core/browser/data_manager/payments/payments_data_manager_unittest.cc
index a01ff8a..ae40d68 100644
--- a/components/autofill/core/browser/data_manager/payments/payments_data_manager_unittest.cc
+++ b/components/autofill/core/browser/data_manager/payments/payments_data_manager_unittest.cc
@@ -48,6 +48,7 @@
 #include "components/autofill/core/browser/suggestions/suggestion.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
 #include "components/autofill/core/browser/ui/autofill_image_fetcher_base.h"
+#include "components/autofill/core/browser/ui/mock_autofill_image_fetcher.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_constants.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
@@ -263,33 +264,6 @@
   std::unique_ptr<PaymentsDataManager> payments_data_manager_;
 };
 
-class MockAutofillImageFetcher : public AutofillImageFetcherBase {
- public:
-  MOCK_METHOD(
-      void,
-      FetchCreditCardArtImagesForURLs,
-      (base::span<const GURL> card_art_urls,
-       base::span<const AutofillImageFetcherBase::ImageSize> image_sizes),
-      (override));
-  MOCK_METHOD(void,
-              FetchPixAccountImagesForURLs,
-              (base::span<const GURL> card_art_urls),
-              (override));
-  MOCK_METHOD(void,
-              FetchValuableImagesForURLs,
-              (base::span<const GURL> image_urls),
-              (override));
-  MOCK_METHOD(const gfx::Image*,
-              GetCachedImageForUrl,
-              (const GURL& image_url, ImageType image_type),
-              (const, override));
-#if BUILDFLAG(IS_ANDROID)
-  MOCK_METHOD(base::android::ScopedJavaLocalRef<jobject>,
-              GetOrCreateJavaImageFetcher,
-              (),
-              (override));
-#endif
-};
 class PaymentsDataManagerTest : public PaymentsDataManagerHelper,
                                 public testing::Test {
  public:
diff --git a/components/autofill/core/browser/data_manager/valuables/test_valuables_data_manager.cc b/components/autofill/core/browser/data_manager/valuables/test_valuables_data_manager.cc
index f901f6a..088b002 100644
--- a/components/autofill/core/browser/data_manager/valuables/test_valuables_data_manager.cc
+++ b/components/autofill/core/browser/data_manager/valuables/test_valuables_data_manager.cc
@@ -7,7 +7,8 @@
 namespace autofill {
 
 TestValuablesDataManager::TestValuablesDataManager()
-    : ValuablesDataManager(/*webdata_service=*/nullptr) {}
+    : ValuablesDataManager(/*webdata_service=*/nullptr,
+                           /*image_fetcher=*/nullptr) {}
 TestValuablesDataManager::~TestValuablesDataManager() = default;
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/data_manager/valuables/valuables_data_manager.cc b/components/autofill/core/browser/data_manager/valuables/valuables_data_manager.cc
index 866fb86..bedd770a 100644
--- a/components/autofill/core/browser/data_manager/valuables/valuables_data_manager.cc
+++ b/components/autofill/core/browser/data_manager/valuables/valuables_data_manager.cc
@@ -5,6 +5,7 @@
 #include "components/autofill/core/browser/data_manager/valuables/valuables_data_manager.h"
 
 #include "components/autofill/core/browser/data_model/valuables/loyalty_card.h"
+#include "components/autofill/core/browser/ui/autofill_image_fetcher_base.h"
 #include "components/autofill/core/browser/webdata/autofill_change.h"
 #include "components/sync/base/features.h"
 #include "components/webdata/common/web_data_results.h"
@@ -12,8 +13,10 @@
 namespace autofill {
 
 ValuablesDataManager::ValuablesDataManager(
-    scoped_refptr<AutofillWebDataService> webdata_service)
-    : webdata_service_(std::move(webdata_service)) {
+    scoped_refptr<AutofillWebDataService> webdata_service,
+    AutofillImageFetcherBase* image_fetcher)
+    : webdata_service_(std::move(webdata_service)),
+      image_fetcher_(image_fetcher) {
   if (!webdata_service_) {
     // In some tests, there are no dbs.
     return;
@@ -30,6 +33,18 @@
   return loyalty_cards_;
 }
 
+const gfx::Image* ValuablesDataManager::GetCachedValuableImageForUrl(
+    const GURL& image_url) const {
+  if (!image_url.is_valid()) {
+    return nullptr;
+  }
+  if (!image_fetcher_) {
+    return nullptr;
+  }
+  return image_fetcher_->GetCachedImageForUrl(
+      image_url, AutofillImageFetcherBase::ImageType::kValuableImage);
+}
+
 void ValuablesDataManager::OnDataRetrieved(
     WebDataServiceBase::Handle handle,
     std::unique_ptr<WDTypedResult> result) {
@@ -57,12 +72,6 @@
 void ValuablesDataManager::OnLoyaltyCardsLoaded(
     const std::vector<LoyaltyCard>& loyalty_cards) {
   loyalty_cards_ = loyalty_cards;
-  // Store loyalty cards sorted by merchant name as they are always
-  // shown in this order.
-  std::ranges::sort(loyalty_cards_,
-                    [](const LoyaltyCard& a, const LoyaltyCard& b) {
-                      return a.merchant_name() < b.merchant_name();
-                    });
   NotifyObservers();
 }
 
diff --git a/components/autofill/core/browser/data_manager/valuables/valuables_data_manager.h b/components/autofill/core/browser/data_manager/valuables/valuables_data_manager.h
index df5a4ca..dc98e72e 100644
--- a/components/autofill/core/browser/data_manager/valuables/valuables_data_manager.h
+++ b/components/autofill/core/browser/data_manager/valuables/valuables_data_manager.h
@@ -14,6 +14,7 @@
 #include "base/observer_list_types.h"
 #include "base/scoped_observation.h"
 #include "components/autofill/core/browser/data_model/valuables/loyalty_card.h"
+#include "components/autofill/core/browser/ui/autofill_image_fetcher_base.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -37,8 +38,8 @@
     virtual void OnValuablesDataChanged() = 0;
   };
 
-  explicit ValuablesDataManager(
-      scoped_refptr<AutofillWebDataService> webdata_service);
+  ValuablesDataManager(scoped_refptr<AutofillWebDataService> webdata_service,
+                       AutofillImageFetcherBase* image_fetcher);
   ValuablesDataManager(const ValuablesDataManager&) = delete;
   ValuablesDataManager& operator=(const ValuablesDataManager&) = delete;
   ~ValuablesDataManager() override;
@@ -55,6 +56,12 @@
   // The returned span may be invalidated asynchronously.
   base::span<const LoyaltyCard> GetLoyaltyCards() const;
 
+  // Returns the cached image for the `image_url` if it was synced locally to
+  // the client. The image is extracted from the local cache in
+  // `AutofillImageFetcher`. If the card art image is not present in the cache,
+  // this function will return a nullptr.
+  const gfx::Image* GetCachedValuableImageForUrl(const GURL& image_url) const;
+
   // AutofillWebDataServiceObserverOnUISequence:
   void OnAutofillChangedBySync(syncer::DataType data_type) override;
 
@@ -86,9 +93,11 @@
   base::ObserverList<Observer> observers_;
 
   // The result of the last successful `LoadLoyaltyCards()` query.
-  // Stored loyalty cards are sorted by merchant name.
   std::vector<LoyaltyCard> loyalty_cards_;
 
+  // The image fetcher to fetch customized images for Autofill data.
+  const raw_ptr<AutofillImageFetcherBase> image_fetcher_ = nullptr;
+
   base::WeakPtrFactory<ValuablesDataManager> weak_ptr_factory_{this};
 };
 
diff --git a/components/autofill/core/browser/data_manager/valuables/valuables_data_manager_unittest.cc b/components/autofill/core/browser/data_manager/valuables/valuables_data_manager_unittest.cc
index 01a8962..5a03024 100644
--- a/components/autofill/core/browser/data_manager/valuables/valuables_data_manager_unittest.cc
+++ b/components/autofill/core/browser/data_manager/valuables/valuables_data_manager_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/test/task_environment.h"
 #include "components/autofill/core/browser/data_model/valuables/loyalty_card.h"
 #include "components/autofill/core/browser/test_utils/valuables_data_test_utils.h"
+#include "components/autofill/core/browser/ui/mock_autofill_image_fetcher.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_backend.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service_test_helper.h"
@@ -25,7 +26,6 @@
 
 namespace autofill {
 namespace {
-using ::testing::ElementsAre;
 using ::testing::IsEmpty;
 using ::testing::UnorderedElementsAre;
 
@@ -56,12 +56,15 @@
     return *helper().autofill_webdata_service();
   }
 
+  MockAutofillImageFetcher& image_fetcher() { return mock_image_fetcher; }
+
   ValuablesTable& valuables_table() { return *valuables_table_; }
 
  private:
   base::test::TaskEnvironment task_environment_;
   base::test::ScopedFeatureList scoped_feature_list{
       syncer::kSyncAutofillLoyaltyCard};
+  MockAutofillImageFetcher mock_image_fetcher;
   raw_ptr<ValuablesTable> valuables_table_;
   std::unique_ptr<AutofillWebDataServiceTestHelper> helper_;
 };
@@ -74,7 +77,8 @@
 
   valuables_table().SetLoyaltyCards({card1, card2});
 
-  ValuablesDataManager valuables_data_manager(&webdata_service());
+  ValuablesDataManager valuables_data_manager(&webdata_service(),
+                                              &image_fetcher());
   EXPECT_THAT(valuables_data_manager.GetLoyaltyCards(), IsEmpty());
 
   helper().WaitUntilIdle();
@@ -88,7 +92,8 @@
   const LoyaltyCard card1 = test::CreateLoyaltyCard();
   valuables_table().SetLoyaltyCards({card1});
 
-  ValuablesDataManager valuables_data_manager(&webdata_service());
+  ValuablesDataManager valuables_data_manager(&webdata_service(),
+                                              &image_fetcher());
   helper().WaitUntilIdle();
   EXPECT_THAT(valuables_data_manager.GetLoyaltyCards(),
               UnorderedElementsAre(card1));
@@ -102,7 +107,7 @@
 
   const LoyaltyCard card2 = test::CreateLoyaltyCard2();
   // Loyalty cards are passed unsorted by sync.
-  valuables_table().SetLoyaltyCards({card2, card1});
+  valuables_table().SetLoyaltyCards({card1, card2});
   // Make sure all async tasks are executed.
   helper().WaitUntilIdle();
 
@@ -119,9 +124,21 @@
   //   the UI sequence.
   helper().WaitUntilIdle();
   helper().WaitUntilIdle();
-  // Assert the cards are stored sorted by merchant name.
   EXPECT_THAT(valuables_data_manager.GetLoyaltyCards(),
-              ElementsAre(card1, card2));
+              UnorderedElementsAre(card1, card2));
+}
+
+TEST_F(ValuablesDataManagerTest, GetCachedValuableImageForUrl) {
+  ValuablesDataManager valuables_data_manager(&webdata_service(),
+                                              &image_fetcher());
+  helper().WaitUntilIdle();
+
+  const GURL expected_url = GURL("https://example.image");
+  EXPECT_CALL(
+      image_fetcher(),
+      GetCachedImageForUrl(
+          expected_url, AutofillImageFetcherBase::ImageType::kValuableImage));
+  valuables_data_manager.GetCachedValuableImageForUrl(expected_url);
 }
 
 }  // namespace
diff --git a/components/autofill/core/browser/data_model/autofill_ai/entity_instance.cc b/components/autofill/core/browser/data_model/autofill_ai/entity_instance.cc
index 0210362..329e3d5 100644
--- a/components/autofill/core/browser/data_model/autofill_ai/entity_instance.cc
+++ b/components/autofill/core/browser/data_model/autofill_ai/entity_instance.cc
@@ -245,7 +245,7 @@
     base::Uuid guid,
     std::string nickname,
     base::Time date_modified,
-    int use_count,
+    size_t use_count,
     base::Time use_date)
     : type_(type),
       attributes_(std::move(attributes)),
@@ -448,10 +448,11 @@
 
 bool EntityInstance::RankingOrder::operator()(const EntityInstance& lhs,
                                               const EntityInstance& rhs) const {
-  // Gets the ranking score of an entity.
+  // At days_since_last_use = 0, use_count = 0, the score is -1.
+  // As days_since_last_use increases, the score becomes more negative.
+  // As use_count increases, the score approaches 0.
   auto get_ranking_score = [&](const EntityInstance& entity) {
-    int days_since_last_use =
-        now_ <= entity.use_date() ? 0 : (now_ - entity.use_date()).InDays();
+    int days_since_last_use = std::max(0, (now_ - entity.use_date()).InDays());
     // The numerator punishes old usages, since as days_since_last_use
     // grows, the score becomes smaller (note the negative sign). The
     // denominator softens this penalty by making it smaller the more often a
diff --git a/components/autofill/core/browser/data_model/autofill_ai/entity_instance.h b/components/autofill/core/browser/data_model/autofill_ai/entity_instance.h
index d2b0018..f687564 100644
--- a/components/autofill/core/browser/data_model/autofill_ai/entity_instance.h
+++ b/components/autofill/core/browser/data_model/autofill_ai/entity_instance.h
@@ -221,7 +221,7 @@
                  base::Uuid guid,
                  std::string nickname,
                  base::Time date_modified,
-                 int use_count,
+                 size_t use_count,
                  base::Time use_date);
 
   EntityInstance(const EntityInstance&);
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 ae35ceb3..9d510f9 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
@@ -2024,7 +2024,8 @@
         std::make_unique<AutofillWebDataServiceTestHelper>(
             std::move(valuables_table));
     client().set_valuables_data_manager(std::make_unique<ValuablesDataManager>(
-        web_data_service_helper_->autofill_webdata_service()));
+        web_data_service_helper_->autofill_webdata_service(),
+        /*image_fetcher=*/nullptr));
     web_data_service_helper_->WaitUntilIdle();
   }
 
diff --git a/components/autofill/core/browser/metrics/payments/card_unmask_authentication_metrics.cc b/components/autofill/core/browser/metrics/payments/card_unmask_authentication_metrics.cc
index 5e00b71a..1fae3d0d 100644
--- a/components/autofill/core/browser/metrics/payments/card_unmask_authentication_metrics.cc
+++ b/components/autofill/core/browser/metrics/payments/card_unmask_authentication_metrics.cc
@@ -90,12 +90,17 @@
       duration);
 }
 
-void LogOtpInputDialogShown(CardUnmaskChallengeOptionType type) {
+void LogOtpInputDialogShown(CreditCard::RecordType card_type,
+                            CardUnmaskChallengeOptionType type) {
   base::UmaHistogramBoolean(
-      "Autofill.OtpInputDialog." + GetOtpAuthType(type) + ".Shown", true);
+      base::StrCat({"Autofill.OtpInputDialog",
+                    AutofillMetrics::GetHistogramStringForCardType(card_type),
+                    ".", GetOtpAuthType(type), ".Shown"}),
+      true);
 }
 
-void LogOtpInputDialogResult(OtpInputDialogResult result,
+void LogOtpInputDialogResult(CreditCard::RecordType card_type,
+                             OtpInputDialogResult result,
                              bool temporary_error_shown,
                              CardUnmaskChallengeOptionType type) {
   DCHECK_GT(result, OtpInputDialogResult::kUnknown);
@@ -104,25 +109,36 @@
                                                  ? ".WithPreviousTemporaryError"
                                                  : ".WithNoTemporaryError";
   base::UmaHistogramEnumeration(
-      "Autofill.OtpInputDialog." + GetOtpAuthType(type) + ".Result", result);
-  base::UmaHistogramEnumeration("Autofill.OtpInputDialog." +
-                                    GetOtpAuthType(type) + ".Result" +
-                                    temporary_error_shown_suffix,
-                                result);
+      base::StrCat({"Autofill.OtpInputDialog",
+                    AutofillMetrics::GetHistogramStringForCardType(card_type),
+                    ".", GetOtpAuthType(type), ".Result"}),
+      result);
+  base::UmaHistogramEnumeration(
+      base::StrCat({"Autofill.OtpInputDialog",
+                    AutofillMetrics::GetHistogramStringForCardType(card_type),
+                    ".", GetOtpAuthType(type), ".Result",
+                    temporary_error_shown_suffix}),
+      result);
 }
 
-void LogOtpInputDialogErrorMessageShown(OtpInputDialogError error,
+void LogOtpInputDialogErrorMessageShown(CreditCard::RecordType card_type,
+                                        OtpInputDialogError error,
                                         CardUnmaskChallengeOptionType type) {
   DCHECK_GT(error, OtpInputDialogError::kUnknown);
   DCHECK_LE(error, OtpInputDialogError::kMaxValue);
   base::UmaHistogramEnumeration(
-      "Autofill.OtpInputDialog." + GetOtpAuthType(type) + ".ErrorMessageShown",
+      base::StrCat({"Autofill.OtpInputDialog",
+                    AutofillMetrics::GetHistogramStringForCardType(card_type),
+                    ".", GetOtpAuthType(type), ".ErrorMessageShown"}),
       error);
 }
 
-void LogOtpInputDialogNewOtpRequested(CardUnmaskChallengeOptionType type) {
+void LogOtpInputDialogNewOtpRequested(CreditCard::RecordType card_type,
+                                      CardUnmaskChallengeOptionType type) {
   base::UmaHistogramBoolean(
-      "Autofill.OtpInputDialog." + GetOtpAuthType(type) + ".NewOtpRequested",
+      base::StrCat({"Autofill.OtpInputDialog",
+                    AutofillMetrics::GetHistogramStringForCardType(card_type),
+                    ".", GetOtpAuthType(type), ".NewOtpRequested"}),
       true);
 }
 
diff --git a/components/autofill/core/browser/metrics/payments/card_unmask_authentication_metrics.h b/components/autofill/core/browser/metrics/payments/card_unmask_authentication_metrics.h
index 16b3f89..097d73f 100644
--- a/components/autofill/core/browser/metrics/payments/card_unmask_authentication_metrics.h
+++ b/components/autofill/core/browser/metrics/payments/card_unmask_authentication_metrics.h
@@ -152,20 +152,24 @@
     CardUnmaskChallengeOptionType type);
 
 // Logs whenever the OTP input dialog is triggered and it is shown.
-void LogOtpInputDialogShown(CardUnmaskChallengeOptionType type);
+void LogOtpInputDialogShown(CreditCard::RecordType card_type,
+                            CardUnmaskChallengeOptionType type);
 
 // Logs the result of how the dialog is dismissed.
-void LogOtpInputDialogResult(OtpInputDialogResult result,
+void LogOtpInputDialogResult(CreditCard::RecordType card_type,
+                             OtpInputDialogResult result,
                              bool temporary_error_shown,
                              CardUnmaskChallengeOptionType type);
 
 // Logs when the temporary error shown in the dialog.
-void LogOtpInputDialogErrorMessageShown(OtpInputDialogError error,
+void LogOtpInputDialogErrorMessageShown(CreditCard::RecordType card_type,
+                                        OtpInputDialogError error,
                                         CardUnmaskChallengeOptionType type);
 
 // Logs when the "Get New Code" button in the dialog is clicked and user is
 // requesting a new OTP.
-void LogOtpInputDialogNewOtpRequested(CardUnmaskChallengeOptionType type);
+void LogOtpInputDialogNewOtpRequested(CreditCard::RecordType card_type,
+                                      CardUnmaskChallengeOptionType type);
 
 // Generate the OTP auth type string according to the challenge option type.
 // This is used as a helper function for LogOtp methods.
diff --git a/components/autofill/core/browser/payments/credit_card_otp_authenticator.cc b/components/autofill/core/browser/payments/credit_card_otp_authenticator.cc
index 293b9e9..1cf22df 100644
--- a/components/autofill/core/browser/payments/credit_card_otp_authenticator.cc
+++ b/components/autofill/core/browser/payments/credit_card_otp_authenticator.cc
@@ -263,7 +263,8 @@
   }
 
   autofill_client_->GetPaymentsAutofillClient()->ShowCardUnmaskOtpInputDialog(
-      selected_challenge_option_, weak_ptr_factory_.GetWeakPtr());
+      card_->record_type(), selected_challenge_option_,
+      weak_ptr_factory_.GetWeakPtr());
 }
 
 void CreditCardOtpAuthenticator::OnDidGetUnmaskRiskData(
diff --git a/components/autofill/core/browser/payments/payments_autofill_client.cc b/components/autofill/core/browser/payments/payments_autofill_client.cc
index 308284f..6af5aad0 100644
--- a/components/autofill/core/browser/payments/payments_autofill_client.cc
+++ b/components/autofill/core/browser/payments/payments_autofill_client.cc
@@ -118,6 +118,7 @@
     base::OnceClosure no_interactive_authentication_callback) {}
 
 void PaymentsAutofillClient::ShowCardUnmaskOtpInputDialog(
+    CreditCard::RecordType card_type,
     const CardUnmaskChallengeOption& challenge_option,
     base::WeakPtr<OtpUnmaskDelegate> delegate) {}
 
diff --git a/components/autofill/core/browser/payments/payments_autofill_client.h b/components/autofill/core/browser/payments/payments_autofill_client.h
index cb510c8..2cbb5ca 100644
--- a/components/autofill/core/browser/payments/payments_autofill_client.h
+++ b/components/autofill/core/browser/payments/payments_autofill_client.h
@@ -398,6 +398,7 @@
 
   // Show the OTP unmask dialog to accept user-input OTP value.
   virtual void ShowCardUnmaskOtpInputDialog(
+      CreditCard::RecordType card_type,
       const CardUnmaskChallengeOption& challenge_option,
       base::WeakPtr<OtpUnmaskDelegate> delegate);
 
diff --git a/components/autofill/core/browser/payments/test_payments_autofill_client.cc b/components/autofill/core/browser/payments/test_payments_autofill_client.cc
index 8559f0e..f0e0c38 100644
--- a/components/autofill/core/browser/payments/test_payments_autofill_client.cc
+++ b/components/autofill/core/browser/payments/test_payments_autofill_client.cc
@@ -115,6 +115,7 @@
 }
 
 void TestPaymentsAutofillClient::ShowCardUnmaskOtpInputDialog(
+    CreditCard::RecordType card_type,
     const CardUnmaskChallengeOption& challenge_option,
     base::WeakPtr<OtpUnmaskDelegate> delegate) {
   show_otp_input_dialog_ = true;
diff --git a/components/autofill/core/browser/payments/test_payments_autofill_client.h b/components/autofill/core/browser/payments/test_payments_autofill_client.h
index 26cca1a..ba1228c7 100644
--- a/components/autofill/core/browser/payments/test_payments_autofill_client.h
+++ b/components/autofill/core/browser/payments/test_payments_autofill_client.h
@@ -87,6 +87,7 @@
       base::OnceClosure no_user_perceived_authentication_callback) override;
   void ShowAutofillErrorDialog(AutofillErrorDialogContext context) override;
   void ShowCardUnmaskOtpInputDialog(
+      CreditCard::RecordType card_type,
       const CardUnmaskChallengeOption& challenge_option,
       base::WeakPtr<OtpUnmaskDelegate> delegate) override;
   PaymentsWindowManager* GetPaymentsWindowManager() override;
diff --git a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.cc b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.cc
index 31a3fcc..0acbad9 100644
--- a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.cc
+++ b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator.cc
@@ -66,6 +66,10 @@
   }
   std::vector<LoyaltyCard> partitionable_cards(loyalty_cards.begin(),
                                                loyalty_cards.end());
+  std::ranges::sort(partitionable_cards,
+                    [](const LoyaltyCard& a, const LoyaltyCard& b) {
+                      return a.merchant_name() < b.merchant_name();
+                    });
   auto non_affiliated_cards = std::ranges::stable_partition(
       partitionable_cards, [&](const LoyaltyCard& card) {
         return LoyaltyCardMatchesDomain(card, url);
diff --git a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator_unittest.cc
index 2c4ee092..1111421 100644
--- a/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/suggestions/valuables/valuable_suggestion_generator_unittest.cc
@@ -72,21 +72,21 @@
   EXPECT_THAT(
       GetLoyaltyCardSuggestions(
           loyalty_cards, GURL("https://not-existing-domain.example/test")),
-      testing::ElementsAre(lc1_suggestion_matcher, lc3_suggestion_matcher,
-                           lc2_suggestion_matcher, separatorMatcher,
+      testing::ElementsAre(lc1_suggestion_matcher, lc2_suggestion_matcher,
+                           lc3_suggestion_matcher, separatorMatcher,
                            manageLoyaltyCardsMatcher));
   EXPECT_THAT(
       GetLoyaltyCardSuggestions(loyalty_cards,
                                 GURL("https://domain2.example/test")),
-      testing::ElementsAre(lc3_suggestion_matcher, lc2_suggestion_matcher,
+      testing::ElementsAre(lc2_suggestion_matcher, lc3_suggestion_matcher,
                            separatorMatcher, lc1_suggestion_matcher,
                            separatorMatcher, manageLoyaltyCardsMatcher));
 
   EXPECT_THAT(
       GetLoyaltyCardSuggestions(loyalty_cards,
                                 GURL("https://common-domain.example/test")),
-      testing::ElementsAre(lc1_suggestion_matcher, lc3_suggestion_matcher,
-                           lc2_suggestion_matcher, separatorMatcher,
+      testing::ElementsAre(lc1_suggestion_matcher, lc2_suggestion_matcher,
+                           lc3_suggestion_matcher, separatorMatcher,
                            manageLoyaltyCardsMatcher));
 }
 
diff --git a/components/autofill/core/browser/ui/mock_autofill_image_fetcher.cc b/components/autofill/core/browser/ui/mock_autofill_image_fetcher.cc
new file mode 100644
index 0000000..3aa0088
--- /dev/null
+++ b/components/autofill/core/browser/ui/mock_autofill_image_fetcher.cc
@@ -0,0 +1,13 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/ui/mock_autofill_image_fetcher.h"
+
+namespace autofill {
+
+MockAutofillImageFetcher::MockAutofillImageFetcher() = default;
+
+MockAutofillImageFetcher::~MockAutofillImageFetcher() = default;
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/ui/mock_autofill_image_fetcher.h b/components/autofill/core/browser/ui/mock_autofill_image_fetcher.h
new file mode 100644
index 0000000..2664727
--- /dev/null
+++ b/components/autofill/core/browser/ui/mock_autofill_image_fetcher.h
@@ -0,0 +1,49 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_UI_MOCK_AUTOFILL_IMAGE_FETCHER_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_UI_MOCK_AUTOFILL_IMAGE_FETCHER_H_
+
+#include "base/containers/span.h"
+#include "components/autofill/core/browser/ui/autofill_image_fetcher_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "ui/gfx/image/image.h"
+#include "url/gurl.h"
+
+namespace autofill {
+
+// Mock version of AutofillImageFetcherBase.
+class MockAutofillImageFetcher : public AutofillImageFetcherBase {
+ public:
+  MockAutofillImageFetcher();
+  ~MockAutofillImageFetcher() override;
+
+  MOCK_METHOD(
+      void,
+      FetchCreditCardArtImagesForURLs,
+      (base::span<const GURL> card_art_urls,
+       base::span<const AutofillImageFetcherBase::ImageSize> image_sizes),
+      (override));
+  MOCK_METHOD(void,
+              FetchPixAccountImagesForURLs,
+              (base::span<const GURL> card_art_urls),
+              (override));
+  MOCK_METHOD(void,
+              FetchValuableImagesForURLs,
+              (base::span<const GURL> image_urls),
+              (override));
+  MOCK_METHOD(const gfx::Image*,
+              GetCachedImageForUrl,
+              (const GURL& image_url, ImageType image_type),
+              (const, override));
+#if BUILDFLAG(IS_ANDROID)
+  MOCK_METHOD(base::android::ScopedJavaLocalRef<jobject>,
+              GetOrCreateJavaImageFetcher,
+              (),
+              (override));
+#endif
+};
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_UI_MOCK_AUTOFILL_IMAGE_FETCHER_H_
diff --git a/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.cc b/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.cc
index f2a8bada..b680c9c6 100644
--- a/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.cc
+++ b/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.cc
@@ -17,11 +17,13 @@
 namespace autofill {
 
 CardUnmaskOtpInputDialogControllerImpl::CardUnmaskOtpInputDialogControllerImpl(
+    const CreditCard::RecordType card_type,
     const CardUnmaskChallengeOption& challenge_option,
     base::WeakPtr<OtpUnmaskDelegate> delegate)
     : challenge_type_(challenge_option.type),
       otp_length_(challenge_option.challenge_input_length),
-      delegate_(delegate) {}
+      delegate_(delegate),
+      card_type_(card_type) {}
 
 CardUnmaskOtpInputDialogControllerImpl::
     ~CardUnmaskOtpInputDialogControllerImpl() {
@@ -46,7 +48,7 @@
 
   dialog_view_ = std::move(create_and_show_view_callback).Run();
   if (dialog_view_) {
-    autofill_metrics::LogOtpInputDialogShown(challenge_type_);
+    autofill_metrics::LogOtpInputDialogShown(card_type_, challenge_type_);
   }
 }
 
@@ -71,6 +73,7 @@
     case OtpUnmaskResult::kOtpMismatch:
       temporary_error_shown_ = true;
       autofill_metrics::LogOtpInputDialogErrorMessageShown(
+          card_type_,
           result == OtpUnmaskResult::kOtpMismatch
               ? autofill_metrics::OtpInputDialogError::kOtpMismatchError
               : autofill_metrics::OtpInputDialogError::kOtpExpiredError,
@@ -91,6 +94,7 @@
 
   if (user_closed_dialog) {
     autofill_metrics::LogOtpInputDialogResult(
+        card_type_,
         ok_button_clicked_ ? autofill_metrics::OtpInputDialogResult::
                                  kDialogCancelledByUserAfterConfirmation
                            : autofill_metrics::OtpInputDialogResult::
@@ -98,11 +102,13 @@
         temporary_error_shown_, challenge_type_);
   } else if (server_request_succeeded) {
     autofill_metrics::LogOtpInputDialogResult(
+        card_type_,
         autofill_metrics::OtpInputDialogResult::
             kDialogClosedAfterVerificationSucceeded,
         temporary_error_shown_, challenge_type_);
   } else {
     autofill_metrics::LogOtpInputDialogResult(
+        card_type_,
         autofill_metrics::OtpInputDialogResult::
             kDialogClosedAfterVerificationFailed,
         temporary_error_shown_, challenge_type_);
@@ -128,7 +134,8 @@
     delegate_->OnNewOtpRequested();
   }
 
-  autofill_metrics::LogOtpInputDialogNewOtpRequested(challenge_type_);
+  autofill_metrics::LogOtpInputDialogNewOtpRequested(card_type_,
+                                                     challenge_type_);
 }
 
 std::u16string CardUnmaskOtpInputDialogControllerImpl::GetWindowTitle() const {
diff --git a/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.h b/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.h
index 73b589a..8e73e79e 100644
--- a/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.h
+++ b/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.h
@@ -5,11 +5,11 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_UI_PAYMENTS_CARD_UNMASK_OTP_INPUT_DIALOG_CONTROLLER_IMPL_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_UI_PAYMENTS_CARD_UNMASK_OTP_INPUT_DIALOG_CONTROLLER_IMPL_H_
 
-#include "components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller.h"
-
 #include "base/functional/callback.h"
 #include "build/build_config.h"
+#include "components/autofill/core/browser/data_model/payments/credit_card.h"
 #include "components/autofill/core/browser/payments/card_unmask_challenge_option.h"
+#include "components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller.h"
 
 namespace autofill {
 
@@ -21,6 +21,7 @@
     : public CardUnmaskOtpInputDialogController {
  public:
   CardUnmaskOtpInputDialogControllerImpl(
+      CreditCard::RecordType card_type,
       const CardUnmaskChallengeOption& challenge_option,
       base::WeakPtr<OtpUnmaskDelegate> delegate);
   CardUnmaskOtpInputDialogControllerImpl(
@@ -90,6 +91,9 @@
   // logging.
   bool ok_button_clicked_ = false;
 
+  // Card type required for logging.
+  const CreditCard::RecordType card_type_;
+
   base::WeakPtrFactory<CardUnmaskOtpInputDialogControllerImpl>
       weak_ptr_factory_{this};
 };
diff --git a/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl_unittest.cc b/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl_unittest.cc
index 900b977..8fbdd6c 100644
--- a/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl_unittest.cc
+++ b/components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.h"
 
+#include "base/strings/strcat.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
 #include "components/autofill/core/browser/metrics/payments/card_unmask_authentication_metrics.h"
@@ -35,7 +36,8 @@
 
 class CardUnmaskOtpInputDialogControllerImplTest
     : public testing::Test,
-      public testing::WithParamInterface<CardUnmaskChallengeOptionType> {
+      public testing::WithParamInterface<
+          std::tuple<CardUnmaskChallengeOptionType, CreditCard::RecordType>> {
  public:
   CardUnmaskOtpInputDialogControllerImplTest() = default;
   CardUnmaskOtpInputDialogControllerImplTest(
@@ -46,16 +48,38 @@
 
   void ShowDialog() {
     CardUnmaskChallengeOption challenge_option;
-    challenge_option.type = GetParam();
+    challenge_option.type = std::get<0>(GetParam());
+    CreditCard::RecordType card_type = std::get<1>(GetParam());
     controller_ = std::make_unique<CardUnmaskOtpInputDialogControllerImpl>(
-        challenge_option, /*delegate=*/nullptr);
+        card_type, challenge_option, /*delegate=*/nullptr);
     controller_->ShowDialog(base::BindOnce(
         &CardUnmaskOtpInputDialogControllerImplTest::CreateOtpInputDialogView,
         base::Unretained(this)));
   }
 
   std::string GetOtpAuthType() {
-    return autofill_metrics::GetOtpAuthType(GetParam());
+    switch (std::get<0>(GetParam())) {
+      case CardUnmaskChallengeOptionType::kSmsOtp:
+        return "SmsOtp";
+      case CardUnmaskChallengeOptionType::kEmailOtp:
+        return "EmailOtp";
+      default:
+        NOTREACHED();
+    }
+  }
+
+  std::string GetCardType() {
+    switch (std::get<1>(GetParam())) {
+      case CreditCard::RecordType::kVirtualCard:
+        return "VirtualCard";
+      case CreditCard::RecordType::kFullServerCard:
+      case CreditCard::RecordType::kMaskedServerCard:
+        return "ServerCard";
+      case CreditCard::RecordType::kLocalCard:
+        return "LocalCard";
+      default:
+        NOTREACHED();
+    }
   }
 
   base::WeakPtr<CardUnmaskOtpInputDialogView> CreateOtpInputDialogView() {
@@ -81,14 +105,15 @@
   controller()->OnDialogClosed(/*user_closed_dialog=*/true,
                                /*server_request_succeeded=*/false);
 
+  std::string base_histogram_name = base::StrCat(
+      {"Autofill.OtpInputDialog.", GetCardType(), ".", GetOtpAuthType()});
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() + ".Result",
+      base_histogram_name + ".Result",
       autofill_metrics::OtpInputDialogResult::
           kDialogCancelledByUserBeforeConfirmation,
       1);
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() +
-          ".Result.WithNoTemporaryError",
+      base_histogram_name + ".Result.WithNoTemporaryError",
       autofill_metrics::OtpInputDialogResult::
           kDialogCancelledByUserBeforeConfirmation,
       1);
@@ -104,17 +129,18 @@
   controller()->OnDialogClosed(/*user_closed_dialog=*/true,
                                /*server_request_succeeded=*/false);
 
+  std::string base_histogram_name = base::StrCat(
+      {"Autofill.OtpInputDialog.", GetCardType(), ".", GetOtpAuthType()});
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() + ".ErrorMessageShown",
+      base_histogram_name + ".ErrorMessageShown",
       autofill_metrics::OtpInputDialogError::kOtpMismatchError, 1);
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() + ".Result",
+      base_histogram_name + ".Result",
       autofill_metrics::OtpInputDialogResult::
           kDialogCancelledByUserBeforeConfirmation,
       1);
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() +
-          ".Result.WithPreviousTemporaryError",
+      base_histogram_name + ".Result.WithPreviousTemporaryError",
       autofill_metrics::OtpInputDialogResult::
           kDialogCancelledByUserBeforeConfirmation,
       1);
@@ -131,17 +157,18 @@
   controller()->OnDialogClosed(/*user_closed_dialog=*/true,
                                /*server_request_succeeded=*/false);
 
+  std::string base_histogram_name = base::StrCat(
+      {"Autofill.OtpInputDialog.", GetCardType(), ".", GetOtpAuthType()});
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() + ".ErrorMessageShown",
+      base_histogram_name + ".ErrorMessageShown",
       autofill_metrics::OtpInputDialogError::kOtpExpiredError, 1);
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() + ".Result",
+      base_histogram_name + ".Result",
       autofill_metrics::OtpInputDialogResult::
           kDialogCancelledByUserAfterConfirmation,
       1);
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() +
-          ".Result.WithPreviousTemporaryError",
+      base_histogram_name + ".Result.WithPreviousTemporaryError",
       autofill_metrics::OtpInputDialogResult::
           kDialogCancelledByUserAfterConfirmation,
       1);
@@ -155,14 +182,15 @@
   controller()->OnDialogClosed(/*user_closed_dialog=*/false,
                                /*server_request_succeeded=*/true);
 
+  std::string base_histogram_name = base::StrCat(
+      {"Autofill.OtpInputDialog.", GetCardType(), ".", GetOtpAuthType()});
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() + ".Result",
+      base_histogram_name + ".Result",
       autofill_metrics::OtpInputDialogResult::
           kDialogClosedAfterVerificationSucceeded,
       1);
   histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() +
-          ".Result.WithNoTemporaryError",
+      base_histogram_name + ".Result.WithNoTemporaryError",
       autofill_metrics::OtpInputDialogResult::
           kDialogClosedAfterVerificationSucceeded,
       1);
@@ -176,17 +204,17 @@
   controller()->OnDialogClosed(/*user_closed_dialog=*/false,
                                /*server_request_succeeded=*/false);
 
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() + ".Result",
-      autofill_metrics::OtpInputDialogResult::
-          kDialogClosedAfterVerificationFailed,
-      1);
-  histogram_tester.ExpectUniqueSample("Autofill.OtpInputDialog." +
-                                          GetOtpAuthType() +
-                                          ".Result.WithNoTemporaryError",
+  std::string base_histogram_name = base::StrCat(
+      {"Autofill.OtpInputDialog.", GetCardType(), ".", GetOtpAuthType()});
+  histogram_tester.ExpectUniqueSample(base_histogram_name + ".Result",
                                       autofill_metrics::OtpInputDialogResult::
                                           kDialogClosedAfterVerificationFailed,
                                       1);
+  histogram_tester.ExpectUniqueSample(
+      base_histogram_name + ".Result.WithNoTemporaryError",
+      autofill_metrics::OtpInputDialogResult::
+          kDialogClosedAfterVerificationFailed,
+      1);
 }
 
 TEST_P(CardUnmaskOtpInputDialogControllerImplTest, NewCodeLinkClicked) {
@@ -196,16 +224,23 @@
   DCHECK(controller());
   controller()->OnNewCodeLinkClicked();
 
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.OtpInputDialog." + GetOtpAuthType() + ".NewOtpRequested", true,
-      1);
+  std::string base_histogram_name = base::StrCat(
+      {"Autofill.OtpInputDialog.", GetCardType(), ".", GetOtpAuthType()});
+  histogram_tester.ExpectUniqueSample(base_histogram_name + ".NewOtpRequested",
+                                      true, 1);
 }
 
 INSTANTIATE_TEST_SUITE_P(
     ,
     CardUnmaskOtpInputDialogControllerImplTest,
-    testing::Values(CardUnmaskChallengeOptionType::kSmsOtp,
-                    CardUnmaskChallengeOptionType::kEmailOtp));
+    testing::ValuesIn({
+        std::make_tuple(CardUnmaskChallengeOptionType::kSmsOtp,
+                        CreditCard::RecordType::kVirtualCard),
+        std::make_tuple(CardUnmaskChallengeOptionType::kSmsOtp,
+                        CreditCard::RecordType::kMaskedServerCard),
+        std::make_tuple(CardUnmaskChallengeOptionType::kEmailOtp,
+                        CreditCard::RecordType::kVirtualCard),
+    }));
 
 }  // namespace
 }  // namespace autofill
diff --git a/components/autofill_ai/core/browser/BUILD.gn b/components/autofill_ai/core/browser/BUILD.gn
index 44c639f..248d378f8 100644
--- a/components/autofill_ai/core/browser/BUILD.gn
+++ b/components/autofill_ai/core/browser/BUILD.gn
@@ -13,6 +13,8 @@
       "autofill_ai_import_utils.h",
       "autofill_ai_manager.cc",
       "autofill_ai_manager.h",
+      "autofill_ai_metrics.cc",
+      "autofill_ai_metrics.h",
       "autofill_ai_utils.cc",
       "autofill_ai_utils.h",
       "metrics/autofill_ai_logger.cc",
diff --git a/components/autofill_ai/core/browser/autofill_ai_metrics.cc b/components/autofill_ai/core/browser/autofill_ai_metrics.cc
new file mode 100644
index 0000000..8fc71e4
--- /dev/null
+++ b/components/autofill_ai/core/browser/autofill_ai_metrics.cc
@@ -0,0 +1,20 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_ai/core/browser/autofill_ai_metrics.h"
+
+#include "base/metrics/histogram_functions.h"
+
+namespace autofill_ai {
+
+namespace {
+constexpr char kOptinMetricsPrefix[] = "Autofill.Ai.OptInFunnel";
+}
+
+// static
+void LogOptInFunnelEvent(AutofillAiOptInFunnelEvents event) {
+  base::UmaHistogramEnumeration(kOptinMetricsPrefix, event);
+}
+
+}  // namespace autofill_ai
diff --git a/components/autofill_ai/core/browser/autofill_ai_metrics.h b/components/autofill_ai/core/browser/autofill_ai_metrics.h
new file mode 100644
index 0000000..d0c198c
--- /dev/null
+++ b/components/autofill_ai/core/browser/autofill_ai_metrics.h
@@ -0,0 +1,24 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_AI_CORE_BROWSER_AUTOFILL_AI_METRICS_H_
+#define COMPONENTS_AUTOFILL_AI_CORE_BROWSER_AUTOFILL_AI_METRICS_H_
+
+namespace autofill_ai {
+
+// Logs metrics related to the user seeing an IPH, accepting it and eventually
+// seeing or accepting the FFR dialog.
+enum class AutofillAiOptInFunnelEvents {
+  kIphShown = 0,
+  kFFRDialogShown = 1,
+  kFFRLearnMoreButtonClicked = 2,
+  kFFRDialogAccepted = 3,
+  kMaxValue = kFFRDialogAccepted,
+};
+
+void LogOptInFunnelEvent(AutofillAiOptInFunnelEvents event);
+
+}  // namespace autofill_ai
+
+#endif  // COMPONENTS_AUTOFILL_AI_CORE_BROWSER_AUTOFILL_AI_METRICS_H_
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/GroupedWebsitesSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/GroupedWebsitesSettings.java
index daf5eb4b..6e0273c 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/GroupedWebsitesSettings.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/GroupedWebsitesSettings.java
@@ -51,6 +51,8 @@
     public static final String PREF_SITES_IN_GROUP = "sites_in_group";
     public static final String PREF_RESET_GROUP = "reset_group_button";
 
+    public static final int RWS_ROW_ID = View.generateViewId();
+
     private static @Nullable GroupedWebsitesSettings sPausedInstance;
 
     private WebsiteGroup mSiteGroup;
@@ -360,6 +362,7 @@
                                     getActivity().getLayoutInflater(),
                                     /* showRwsMembershipLabels= */ false,
                                     /* isClickable= */ false);
+                    preference.setViewId(RWS_ROW_ID);
                     // If the row is for the current site, deleting the data will bounce back to the
                     // previous page to refresh
                     preference.setOnDeleteCallback(
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
index e4b2d261..7c848ad 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
@@ -115,6 +115,7 @@
     public static final String PREF_RESET_SITE = "reset_site_button";
 
     public static final int REQUEST_CODE_NOTIFICATION_CHANNEL_SETTINGS = 1;
+    public static final int RWS_ROW_ID = View.generateViewId();
 
     private static boolean arrayContains(int[] array, int element) {
         for (int e : array) {
@@ -1110,6 +1111,7 @@
                                     getActivity().getLayoutInflater(),
                                     /* showRwsMembershipLabels= */ false,
                                     /* isClickable= */ false);
+                    preference.setViewId(RWS_ROW_ID);
                     preference.setOnDeleteCallback(
                             isCurrentSite(entry)
                                     // If deleting data for the current site, pop back to refresh
diff --git a/components/omnibox/browser/featured_search_provider_unittest.cc b/components/omnibox/browser/featured_search_provider_unittest.cc
index 3503541f..cd7cb89 100644
--- a/components/omnibox/browser/featured_search_provider_unittest.cc
+++ b/components/omnibox/browser/featured_search_provider_unittest.cc
@@ -295,7 +295,8 @@
   RunTest(typing_scheme_cases);
 }
 
-TEST_F(FeaturedSearchProviderTest, StarterPackExpansionRelevance) {
+// TODO(crbug.com/413598265): Fix flaky test.
+TEST_F(FeaturedSearchProviderTest, FLAKY_StarterPackExpansionRelevance) {
   base::test::ScopedFeatureList features;
   features.InitWithFeatures(
       {omnibox::kStarterPackExpansion,
diff --git a/components/optimization_guide/core/model_execution/model_execution_fetcher.cc b/components/optimization_guide/core/model_execution/model_execution_fetcher.cc
index 07fbe90..01f7915 100644
--- a/components/optimization_guide/core/model_execution/model_execution_fetcher.cc
+++ b/components/optimization_guide/core/model_execution/model_execution_fetcher.cc
@@ -145,8 +145,42 @@
       // Used for testing purposes. No real features use this.
       return MISSING_TRAFFIC_ANNOTATION;
     case ModelBasedCapabilityKey::kFormsClassifications:
-      // TODO(crbug.com/389631477) - Add traffic annotation.
-      return MISSING_TRAFFIC_ANNOTATION;
+      return net::DefineNetworkTrafficAnnotation(
+          "forms_classifications_model_execution", R"(
+    semantics {
+      sender: "AutofillAI - Forms Classifications"
+      description:
+        "Analyze page content to classify the types of form fields and store "
+        "those classifications for subsequent autofilling of forms."
+      trigger: "User encounters a web form on page load and the Autofill "
+               "server has selected the form as relevant for model execution."
+      destination: GOOGLE_OWNED_SERVICE
+      data: "Title, URL, and content of the page."
+      internal {
+        contacts {
+          email: "chrome-intelligence-core@google.com"
+        }
+      }
+      user_data {
+        type: ACCESS_TOKEN
+        type: SENSITIVE_URL
+        type: WEB_CONTENT
+        type: USER_CONTENT
+      }
+      last_reviewed: "2025-04-23"
+    }
+    policy {
+      cookies_allowed: NO
+      setting:
+        "Users can control this by signing-in to Chrome, and via the "
+        "'Autofill with AI' setting in the 'Autofill and passwords' "
+        "section."
+      chrome_policy {
+        AutofillPredictionSettings {
+          AutofillPredictionSettings: 2
+        }
+      }
+    })");
     case ModelBasedCapabilityKey::kEnhancedCalendar:
       // TODO(crbug.com/398296762): Add network traffic annotation.
       return MISSING_TRAFFIC_ANNOTATION;
diff --git a/components/page_load_metrics/browser/observers/use_counter/webdx_feature_maps.cc b/components/page_load_metrics/browser/observers/use_counter/webdx_feature_maps.cc
index 2ae4e55..329a9a7 100644
--- a/components/page_load_metrics/browser/observers/use_counter/webdx_feature_maps.cc
+++ b/components/page_load_metrics/browser/observers/use_counter/webdx_feature_maps.cc
@@ -17,374 +17,371 @@
 UseCounterMetricsRecorder::GetWebFeatureToWebDXFeatureMap() {
   static const base::NoDestructor<
       const base::flat_map<WebFeature, WebDXFeature>>
-      kMap{
-          {{WebFeature::kViewTransition, WebDXFeature::kViewTransitions},
-           {WebFeature::kValidPopoverAttribute, WebDXFeature::kPopover},
-           {WebFeature::kCSSSubgridLayout, WebDXFeature::kSubgrid},
-           {WebFeature::kCSSCascadeLayers, WebDXFeature::kCascadeLayers},
-           // If the compression or decompression stream constructors were
-           // invoked, WebDXFeature::count that as the CompressionStreams WebDX
-           // feature being used.
-           {WebFeature::kCompressionStreamConstructor,
-            WebDXFeature::kCompressionStreams},
-           {WebFeature::kDecompressionStreamConstructor,
-            WebDXFeature::kCompressionStreams},
-           {WebFeature::kAVIFImage, WebDXFeature::kAvif},
-           {WebFeature::kBlockingAttributeRenderToken,
-            WebDXFeature::kBlockingRender},
-           {WebFeature::kV8BroadcastChannel_Constructor,
-            WebDXFeature::kBroadcastChannel},
-           {WebFeature::kCanvasRenderingContext2DContextLostEvent,
-            WebDXFeature::kCanvasContextLost},
-           {WebFeature::kCSSCascadeLayers, WebDXFeature::kCascadeLayers},
-           {WebFeature::kPressureObserver_Constructor,
-            WebDXFeature::kComputePressure},
-           {WebFeature::kAdoptedStyleSheets,
-            WebDXFeature::kConstructedStylesheets},
-           {WebFeature::kCSSAtRuleContainer, WebDXFeature::kContainerQueries},
-           {WebFeature::kCSSStyleContainerQuery,
-            WebDXFeature::kContainerStyleQueries},
-           {WebFeature::kCSSAtRuleCounterStyle, WebDXFeature::kCounterStyle},
-           {WebFeature::kCreateCSSModuleScript, WebDXFeature::kCssModules},
-           {WebFeature::kStreamingDeclarativeShadowDOM,
-            WebDXFeature::kDeclarativeShadowDom},
-           {WebFeature::kShowPickerSelect, WebDXFeature::kShowPickerSelect},
-           {WebFeature::kHTMLDetailsElementNameAttribute,
-            WebDXFeature::kDetailsName},
-           {WebFeature::kHTMLFencedFrameElement, WebDXFeature::kFencedframe},
-           {WebFeature::kHTMLUnsafeMethods, WebDXFeature::kParseHtmlUnsafe},
-           {WebFeature::kCloseWatcherScriptConstructor,
-            WebDXFeature::kClosewatcher},
-           {WebFeature::kHTMLSearchElement, WebDXFeature::kSearch},
-           {WebFeature::kDialogElement, WebDXFeature::kDialog},
-           {WebFeature::kV8DocumentPictureInPicture_RequestWindow_Method,
-            WebDXFeature::kDocumentPictureInPicture},
-           {WebFeature::kFlexGapSpecified, WebDXFeature::kFlexboxGap},
-           {WebFeature::kCSSFlexibleBox, WebDXFeature::kFlexbox},
-           {WebFeature::kCSSSelectorPseudoFocusVisible,
-            WebDXFeature::kFocusVisible},
-           {WebFeature::kCSSGridLayout, WebDXFeature::kGrid},
-           {WebFeature::kCSSSelectorPseudoHas, WebDXFeature::kHas},
-           {WebFeature::kCSSSelectorPseudoHasSlotted,
-            WebDXFeature::kHasSlotted},
-           {WebFeature::kIdleDetectionStart, WebDXFeature::kIdleDetection},
-           {WebFeature::kImportMap, WebDXFeature::kImportMaps},
-           {WebFeature::kIntersectionObserverV2,
-            WebDXFeature::kIntersectionObserverV2},
-           {WebFeature::kIntersectionObserver_Constructor,
-            WebDXFeature::kIntersectionObserver},
-           {WebFeature::kCSSSelectorPseudoIs, WebDXFeature::kIs},
-           {WebFeature::kPrepareModuleScript, WebDXFeature::kJsModules},
-           {WebFeature::kInstantiateModuleScript, WebDXFeature::kJsModules},
-           {WebFeature::kV8MediaSession_Metadata_AttributeSetter,
-            WebDXFeature::kMediaSession},
-           {WebFeature::kOffscreenCanvas, WebDXFeature::kOffscreenCanvas},
-           {WebFeature::kV8StorageManager_GetDirectory_Method,
-            WebDXFeature::kOriginPrivateFileSystem},
-           {WebFeature::kV8Window_FetchLater_Method, WebDXFeature::kFetchlater},
-           {WebFeature::kV8Window_ShowOpenFilePicker_Method,
-            WebDXFeature::kFileSystemAccess},
-           {WebFeature::kV8Window_ShowSaveFilePicker_Method,
-            WebDXFeature::kFileSystemAccess},
-           {WebFeature::kV8Window_ShowDirectoryPicker_Method,
-            WebDXFeature::kFileSystemAccess},
-           {WebFeature::kV8HTMLVideoElement_RequestPictureInPicture_Method,
-            WebDXFeature::kPictureInPicture},
-           {WebFeature::kV8HTMLVideoElement_GetVideoPlaybackQuality_Method,
-            WebDXFeature::kMediaPlaybackQuality},
-           {WebFeature::kV8Window_QueryLocalFonts_Method,
-            WebDXFeature::kLocalFonts},
-           {WebFeature::kElementRequestPointerLock, WebDXFeature::kPointerLock},
-           {WebFeature::kCSSRelativeColor, WebDXFeature::kRelativeColor},
-           {WebFeature::kCSSAtRuleScope, WebDXFeature::kScope},
-           {WebFeature::kScrollend, WebDXFeature::kScrollend},
-           {WebFeature::kTextFragmentAnchor,
-            WebDXFeature::kScrollToTextFragment},
-           {WebFeature::kV8HTMLInputElement_ShowPicker_Method,
-            WebDXFeature::kShowPickerInput},
-           {WebFeature::kHTMLSlotElement, WebDXFeature::kSlot},
-           {WebFeature::kV8SpeechRecognition_Start_Method,
-            WebDXFeature::kSpeechRecognition},
-           {WebFeature::kV8SpeechSynthesis_Speak_Method,
-            WebDXFeature::kSpeechSynthesis},
-           {WebFeature::kStorageAccessAPI_HasStorageAccess_Method,
-            WebDXFeature::kStorageAccess},
-           {WebFeature::kStorageAccessAPI_requestStorageAccess_Method,
-            WebDXFeature::kStorageAccess},
-           {WebFeature::kStorageAccessAPI_requestStorageAccessFor_Method,
-            WebDXFeature::kStorageAccess},
-           {WebFeature::kStorageAccessAPI_requestStorageAccess_BeyondCookies,
-            WebDXFeature::kNonCookieStorageAccess},
-           {WebFeature::kStorageAccessAPI_hasUnpartitionedCookieAccess,
-            WebDXFeature::kNonCookieStorageAccess},
-           {WebFeature::kStorageBucketsOpen, WebDXFeature::kStorageBuckets},
-           {WebFeature::kCSSSelectorTargetText, WebDXFeature::kTargetText},
-           {WebFeature::kHTMLTemplateElement, WebDXFeature::kTemplate},
-           {WebFeature::kTextWrapBalance, WebDXFeature::kTextWrapBalance},
-           {WebFeature::kTextWrapPretty, WebDXFeature::kTextWrapPretty},
-           {WebFeature::kCSSSelectorUserValid, WebDXFeature::kUserPseudos},
-           {WebFeature::kCSSSelectorUserInvalid, WebDXFeature::kUserPseudos},
-           {WebFeature::kWebCodecs, WebDXFeature::kWebcodecs},
-           {WebFeature::kHidDeviceOpen, WebDXFeature::kWebhid},
-           {WebFeature::kV8LockManager_Request_Method, WebDXFeature::kWebLocks},
-           {WebFeature::kWebPImage, WebDXFeature::kWebp},
-           {WebFeature::kWebTransport, WebDXFeature::kWebtransport},
-           {WebFeature::kUsbDeviceOpen, WebDXFeature::kWebusb},
-           {WebFeature::kVTTCue, WebDXFeature::kWebvtt},
-           {WebFeature::kCSSSelectorPseudoWhere, WebDXFeature::kWhere},
-           {WebFeature::kDataListElement, WebDXFeature::kDatalist},
-           {WebFeature::kCSSSelectorPseudoDir, WebDXFeature::kDirPseudo},
-           {WebFeature::kHiddenUntilFoundAttribute,
-            WebDXFeature::kHiddenUntilFound},
-           {WebFeature::kAbortSignalAny, WebDXFeature::kAbortsignalAny},
-           {WebFeature::kAttributionReportingAPIAll,
-            WebDXFeature::kAttributionReporting},
-           {WebFeature::kNavigationAPI, WebDXFeature::kNavigation},
-           {WebFeature::kMathMLMathElement, WebDXFeature::kMathml},
-           {WebFeature::kCanvasRenderingContext2DConicGradient,
-            WebDXFeature::kCanvasCreateconicgradient},
-           {WebFeature::kCanvasRenderingContext2DReset,
-            WebDXFeature::kCanvasReset},
-           {WebFeature::kCanvasRenderingContext2DRoundRect,
-            WebDXFeature::kCanvasRoundrect},
-           {WebFeature::kCookieStoreAPI, WebDXFeature::kCookieStore},
-           {WebFeature::kCSSColorMixFunction, WebDXFeature::kColorMix},
-           {WebFeature::kImageSet, WebDXFeature::kImageSet},
-           {WebFeature::kStructuredCloneMethod, WebDXFeature::kStructuredClone},
-           {WebFeature::kSlotAssignNode, WebDXFeature::kSlotAssign},
-           {WebFeature::kDeviceMotionSecureOrigin,
-            WebDXFeature::kDeviceOrientationEvents},
-           {WebFeature::kDeviceOrientationAbsoluteSecureOrigin,
-            WebDXFeature::kDeviceOrientationEvents},
-           {WebFeature::kDeviceOrientationSecureOrigin,
-            WebDXFeature::kDeviceOrientationEvents},
-           {WebFeature::kGamepadButtons, WebDXFeature::kGamepad},
-           {WebFeature::kWakeLockAcquireScreenLock,
-            WebDXFeature::kScreenWakeLock},
-           {WebFeature::kWakeLockAcquireSystemLock,
-            WebDXFeature::kScreenWakeLock},
-           {WebFeature::kWebBluetoothRemoteServerConnect,
-            WebDXFeature::kWebBluetooth},
-           {WebFeature::kWebNfcNdefReaderScan, WebDXFeature::kWebNfc},
-           {WebFeature::kWebGPURequestAdapter, WebDXFeature::kWebgpu},
-           {WebFeature::kSerialPortOpen, WebDXFeature::kSerial},
-           {WebFeature::kModuleDedicatedWorker,
-            WebDXFeature::kJsModulesWorkers},
-           {WebFeature::kModuleSharedWorker,
-            WebDXFeature::kJsModulesSharedWorkers},
-           {WebFeature::kCssDisplayPropertyMultipleValues,
-            WebDXFeature::kTwoValueDisplay},
-           {WebFeature::kTwoValuedOverflow, WebDXFeature::kOverflowShorthand},
-           {WebFeature::kKeyboardApiGetLayoutMap, WebDXFeature::kKeyboardMap},
-           {WebFeature::kSchedulerPostTask, WebDXFeature::kScheduler},
-           {WebFeature::kSchedulerYield, WebDXFeature::kScheduler},
-           {WebFeature::kTaskControllerConstructor, WebDXFeature::kScheduler},
-           {WebFeature::kTaskControllerSetPriority, WebDXFeature::kScheduler},
-           {WebFeature::kTaskSignalPriority, WebDXFeature::kScheduler},
-           {WebFeature::kKeyboardApiLock, WebDXFeature::kKeyboardLock},
-           {WebFeature::kAsyncClipboardAPIRead, WebDXFeature::kAsyncClipboard},
-           {WebFeature::kAsyncClipboardAPIWrite, WebDXFeature::kAsyncClipboard},
-           {WebFeature::kAsyncClipboardAPIReadText,
-            WebDXFeature::kAsyncClipboard},
-           {WebFeature::kAsyncClipboardAPIWriteText,
-            WebDXFeature::kAsyncClipboard},
-           {WebFeature::kHtmlClipboardApiUnsanitizedRead,
-            WebDXFeature::kClipboardUnsanitizedFormats},
-           {WebFeature::kClipboardCustomFormatRead,
-            WebDXFeature::kClipboardCustomFormat},
-           {WebFeature::kClipboardCustomFormatWrite,
-            WebDXFeature::kClipboardCustomFormat},
-           {WebFeature::kClipboardSvgRead, WebDXFeature::kClipboardSvg},
-           {WebFeature::kClipboardSvgWrite, WebDXFeature::kClipboardSvg},
-           {WebFeature::kV8ClipboardItem_Supports_Method,
-            WebDXFeature::kClipboardSupports},
-           {WebFeature::kV8AbortController_Constructor,
-            WebDXFeature::kAborting},
-           {WebFeature::kV8AbortSignal_Abort_Method, WebDXFeature::kAborting},
-           {WebFeature::kAbortSignalTimeout, WebDXFeature::kAborting},
-           {WebFeature::kEditContext, WebDXFeature::kEditContext},
-           {WebFeature::kInertAttribute, WebDXFeature::kInert},
-           {WebFeature::kSegmenter, WebDXFeature::kIntlSegmenter},
-           {WebFeature::kEyeDropperOpen, WebDXFeature::kEyedropper},
-           {WebFeature::kElementCheckVisibility,
-            WebDXFeature::kCheckVisibility},
-           {WebFeature::kElementGetHTML, WebDXFeature::kGethtml},
-           {WebFeature::kHTMLCanvasElement, WebDXFeature::kCanvas},
-           {WebFeature::kCanvasUseColorSpace,
-            WebDXFeature::kCanvas2dColorManagement},
-           {WebFeature::kCSSAtRuleStartingStyle, WebDXFeature::kStartingStyle},
-           {WebFeature::kCaretPositionFromPoint,
-            WebDXFeature::kDocumentCaretpositionfrompoint},
-           {WebFeature::kMeasureMemory, WebDXFeature::kMeasureMemory},
-           {WebFeature::kMediaQueryRangeSyntax,
-            WebDXFeature::kMediaQueryRangeSyntax},
-           {WebFeature::kCSSRegisterProperty,
-            WebDXFeature::kRegisteredCustomProperties},
-           {WebFeature::kCSSAtRuleProperty,
-            WebDXFeature::kRegisteredCustomProperties},
-           {WebFeature::kAccelerometerConstructor,
-            WebDXFeature::kAccelerometer},
-           {WebFeature::kLinearAccelerationSensorConstructor,
-            WebDXFeature::kAccelerometer},
-           {WebFeature::kGravitySensorConstructor,
-            WebDXFeature::kAccelerometer},
-           {WebFeature::kV8AudioContext_Constructor, WebDXFeature::kWebAudio},
-           {WebFeature::kV8OfflineAudioContext_Constructor,
-            WebDXFeature::kOfflineAudioContext},
-           {WebFeature::kAudioWorkletNodeConstructor,
-            WebDXFeature::kAudioWorklet},
-           {WebFeature::kMIDIPortOpen, WebDXFeature::kWebMidi},
-           {WebFeature::kV8HTMLVideoElement_RequestVideoFrameCallback_Method,
-            WebDXFeature::kRequestVideoFrameCallback},
-           {WebFeature::kSvgContextFillOrStroke,
-            WebDXFeature::kContextFillStroke},
-           {WebFeature::kHasCapUnits, WebDXFeature::kCap},
-           {WebFeature::kHasRcapUnits, WebDXFeature::kRcap},
-           {WebFeature::kHasIcUnits, WebDXFeature::kIc},
-           {WebFeature::kHasRicUnits, WebDXFeature::kRic},
-           {WebFeature::kHasLhUnits, WebDXFeature::kLh},
-           {WebFeature::kHasRlhUnits, WebDXFeature::kRlh},
-           {WebFeature::kCSSNesting, WebDXFeature::kNesting},
-           {WebFeature::kCSSSelectorNthChildOfSelector,
-            WebDXFeature::kNthChildOf},
-           {WebFeature::kForcedColorsMediaFeature, WebDXFeature::kForcedColors},
-           {WebFeature::kCSSTrigFunctions, WebDXFeature::kTrigFunctions},
-           {WebFeature::kCSSColorGradientColorSpace,
-            WebDXFeature::kGradientInterpolation},
-           {WebFeature::kCSSCalcConstants, WebDXFeature::kCalcConstants},
-           {WebFeature::kHasChUnits, WebDXFeature::kCh},
-           {WebFeature::kHasRchUnits, WebDXFeature::kRch},
-           {WebFeature::kHasRexUnits, WebDXFeature::kRex},
-           {WebFeature::kCSSColorFunction, WebDXFeature::kColorFunction},
-           {WebFeature::kCSSColor_SpaceOkLxx, WebDXFeature::kOklab},
-           {WebFeature::kCSSColor_SpaceLxx, WebDXFeature::kLab},
-           {WebFeature::kCSSColor_SpaceHwb, WebDXFeature::kHwb},
-           {WebFeature::kCSSKeywordRevert, WebDXFeature::kRevertValue},
-           {WebFeature::kPrefersContrastMediaFeature,
-            WebDXFeature::kPrefersContrast},
-           {WebFeature::kPrefersReducedTransparencyMediaFeature,
-            WebDXFeature::kPrefersReducedTransparency},
-           {WebFeature::kCSSRoundModRemFunctions, WebDXFeature::kRoundModRem},
-           {WebFeature::kCSSExponentialFunctions, WebDXFeature::kExpFunctions},
-           {WebFeature::kCSSLinearEasing, WebDXFeature::kLinearEasing},
-           {WebFeature::kOverflowMediaQuery, WebDXFeature::kOverflow},
-           {WebFeature::kHasSpellingOrGrammarErrorPseudoElement,
-            WebDXFeature::kSpellingGrammarError},
-           {WebFeature::kDynamicRangeMediaQuery, WebDXFeature::kDynamicRange},
-           {WebFeature::kHasMarkerPseudoElement, WebDXFeature::kMarker},
-           {WebFeature::kDisplayModeMediaQuery, WebDXFeature::kDisplayMode},
-           {WebFeature::kCSSWordBreakAutoPhrase,
-            WebDXFeature::kWordBreakAutoPhrase},
-           {WebFeature::kCSSSelectorPseudoModal, WebDXFeature::kModal},
-           {WebFeature::kUpdateMediaQuery, WebDXFeature::kUpdate},
-           {WebFeature::kCSSSelectorPseudoFileSelectorButton,
-            WebDXFeature::kFileSelectorButton},
-           {WebFeature::kClipPathGeometryBox, WebDXFeature::kClipPathBoxes},
-           {WebFeature::kActiveViewTransitionPseudo,
-            WebDXFeature::kActiveViewTransition},
-           {WebFeature::kSnapEvent, WebDXFeature::kScrollSnapEvents},
-           {WebFeature::kScrollTimelineConstructor,
-            WebDXFeature::kScrollDrivenAnimations},
-           {WebFeature::kViewTimelineConstructor,
-            WebDXFeature::kScrollDrivenAnimations},
-           {WebFeature::kScrollFunctionTimeline,
-            WebDXFeature::kScrollDrivenAnimations},
-           {WebFeature::kViewFunctionTimeline,
-            WebDXFeature::kScrollDrivenAnimations},
-           {WebFeature::kHighlightAPIRegisterHighlight,
-            WebDXFeature::kHighlight},
-           {WebFeature::kV8Element_Animate_Method,
-            WebDXFeature::kWebAnimations},
-           {WebFeature::kV8Document_GetAnimations_Method,
-            WebDXFeature::kWebAnimations},
-           {WebFeature::kV8ShadowRoot_GetAnimations_Method,
-            WebDXFeature::kWebAnimations},
-           {WebFeature::kV8Element_GetAnimations_Method,
-            WebDXFeature::kWebAnimations},
-           {WebFeature::kV8Animation_Constructor, WebDXFeature::kWebAnimations},
-           {WebFeature::kCSSAtRuleViewTransition,
-            WebDXFeature::kCrossDocumentViewTransitions},
-           {WebFeature::kCSSCalcSizeFunction, WebDXFeature::kCalcSize},
-           {WebFeature::kLongAnimationFrameObserver,
-            WebDXFeature::kLongAnimationFrames},
-           {WebFeature::kLongAnimationFrameRequested,
-            WebDXFeature::kLongAnimationFrames},
-           {WebFeature::kVisibilityStateObserver,
-            WebDXFeature::kPageVisibilityState},
-           {WebFeature::kFedCm, WebDXFeature::kFedcm},
-           {WebFeature::kWebOTP, WebDXFeature::kWebOtp},
-           {WebFeature::kPreservesPitch, WebDXFeature::kPreservesPitch},
-           {WebFeature::kV8HTMLMediaElement_Remote_AttributeGetter,
-            WebDXFeature::kRemotePlayback},
-           {WebFeature::kVerticalFormControls,
-            WebDXFeature::kVerticalFormControls},
-           {WebFeature::kNavigatorPdfViewerEnabled, WebDXFeature::kPdfViewer},
-           {WebFeature::kReferrerPolicyHeader, WebDXFeature::kReferrerPolicy},
-           {WebFeature::kHTMLAnchorElementReferrerPolicyAttribute,
-            WebDXFeature::kReferrerPolicy},
-           {WebFeature::kHTMLElementWritingSuggestions,
-            WebDXFeature::kWritingsuggestions},
-           {WebFeature::kHTMLIFrameElementReferrerPolicyAttribute,
-            WebDXFeature::kReferrerPolicy},
-           {WebFeature::kHTMLImageElementReferrerPolicyAttribute,
-            WebDXFeature::kReferrerPolicy},
-           {WebFeature::kHTMLLinkElementReferrerPolicyAttribute,
-            WebDXFeature::kReferrerPolicy},
-           {WebFeature::kV8Ink_RequestPresenter_Method, WebDXFeature::kInk},
-           {WebFeature::kVirtualKeyboardHide, WebDXFeature::kVirtualKeyboard},
-           {WebFeature::kVirtualKeyboardOverlayPolicy,
-            WebDXFeature::kVirtualKeyboard},
-           {WebFeature::kVirtualKeyboardShow, WebDXFeature::kVirtualKeyboard},
-           {WebFeature::kDurationFormat, WebDXFeature::kIntlDurationFormat},
-           {WebFeature::kWebAppWindowControlsOverlay,
-            WebDXFeature::kWindowControlsOverlay},
-           {WebFeature::kPriorityHints, WebDXFeature::kFetchPriority},
-           {WebFeature::kBarPropLocationbar, WebDXFeature::kBarprop},
-           {WebFeature::kBarPropMenubar, WebDXFeature::kBarprop},
-           {WebFeature::kBarPropPersonalbar, WebDXFeature::kBarprop},
-           {WebFeature::kBarPropScrollbars, WebDXFeature::kBarprop},
-           {WebFeature::kBarPropStatusbar, WebDXFeature::kBarprop},
-           {WebFeature::kBarPropToolbar, WebDXFeature::kBarprop},
-           {WebFeature::kDocumentBeforeUnloadRegistered,
-            WebDXFeature::kBeforeunload},
-           {WebFeature::kLayoutShiftExplicitlyRequested,
-            WebDXFeature::kLayoutInstability},
-           {WebFeature::kJSSelfProfiling, WebDXFeature::kProfiler},
-           {WebFeature::kZstdContentEncoding, WebDXFeature::kZstd},
-           {WebFeature::kColumnPseudoElement, WebDXFeature::kColumnPseudo},
-           {WebFeature::kScrollButtonPseudoElement,
-            WebDXFeature::kScrollButtons},
-           {WebFeature::kScrollMarkerPseudoElement,
-            WebDXFeature::kScrollMarkers},
-           {WebFeature::kPopoverTypeHint, WebDXFeature::kPopoverHint},
-           {WebFeature::kFileHandlingLaunch, WebDXFeature::kAppFileHandlers},
-           {WebFeature::kWebAppManifestProtocolHandlers,
-            WebDXFeature::kAppProtocolHandlers},
-           {WebFeature::kBadgeSetWithoutNotificationPermissionInBrowserWindow,
-            WebDXFeature::kBadging},
-           {WebFeature::kBadgeSetWithoutNotificationPermissionInAppWindow,
-            WebDXFeature::kBadging},
-           {WebFeature::kBadgeSetWithoutNotificationPermissionInWorker,
-            WebDXFeature::kBadging},
-           {WebFeature::kBeforeInstallPromptEvent,
-            WebDXFeature::kBeforeinstallprompt},
-           {WebFeature::kBeforeInstallPromptEventUserChoice,
-            WebDXFeature::kBeforeinstallprompt},
-           {WebFeature::kBeforeInstallPromptEventPreventDefault,
-            WebDXFeature::kBeforeinstallprompt},
-           {WebFeature::kBeforeInstallPromptEventPrompt,
-            WebDXFeature::kBeforeinstallprompt},
-           {WebFeature::kV8BeforeInstallPromptEvent_Platforms_AttributeGetter,
-            WebDXFeature::kBeforeinstallprompt},
-           {WebFeature::kWebAppManifestLaunchHandler,
-            WebDXFeature::kAppLaunchHandler},
-           {WebFeature::kCSSFunctions, WebDXFeature::kFunction},
-           {WebFeature::kSelectionGetComposedRanges,
-            WebDXFeature::kComposedRanges},
-           {WebFeature::kSharedDictionaryUsed,
-            WebDXFeature::kCompressionDictionaryTransport},
-           {WebFeature::kHttp3, WebDXFeature::kHttp3},
-           {WebFeature::kSpeculationRules, WebDXFeature::kSpeculationRules}}};
+      kMap{{
+          {WebFeature::kViewTransition, WebDXFeature::kViewTransitions},
+          {WebFeature::kValidPopoverAttribute, WebDXFeature::kPopover},
+          {WebFeature::kCSSSubgridLayout, WebDXFeature::kSubgrid},
+          {WebFeature::kCSSCascadeLayers, WebDXFeature::kCascadeLayers},
+          // If the compression or decompression stream constructors were
+          // invoked, WebDXFeature::count that as the CompressionStreams WebDX
+          // feature being used.
+          {WebFeature::kCompressionStreamConstructor,
+           WebDXFeature::kCompressionStreams},
+          {WebFeature::kDecompressionStreamConstructor,
+           WebDXFeature::kCompressionStreams},
+          {WebFeature::kAVIFImage, WebDXFeature::kAvif},
+          {WebFeature::kBlockingAttributeRenderToken,
+           WebDXFeature::kBlockingRender},
+          {WebFeature::kV8BroadcastChannel_Constructor,
+           WebDXFeature::kBroadcastChannel},
+          {WebFeature::kCanvasRenderingContext2DContextLostEvent,
+           WebDXFeature::kCanvasContextLost},
+          {WebFeature::kCSSCascadeLayers, WebDXFeature::kCascadeLayers},
+          {WebFeature::kPressureObserver_Constructor,
+           WebDXFeature::kComputePressure},
+          {WebFeature::kAdoptedStyleSheets,
+           WebDXFeature::kConstructedStylesheets},
+          {WebFeature::kCSSAtRuleContainer, WebDXFeature::kContainerQueries},
+          {WebFeature::kCSSStyleContainerQuery,
+           WebDXFeature::kContainerStyleQueries},
+          {WebFeature::kCSSAtRuleCounterStyle, WebDXFeature::kCounterStyle},
+          {WebFeature::kCreateCSSModuleScript, WebDXFeature::kCssModules},
+          {WebFeature::kStreamingDeclarativeShadowDOM,
+           WebDXFeature::kDeclarativeShadowDom},
+          {WebFeature::kShowPickerSelect, WebDXFeature::kShowPickerSelect},
+          {WebFeature::kHTMLDetailsElementNameAttribute,
+           WebDXFeature::kDetailsName},
+          {WebFeature::kHTMLFencedFrameElement, WebDXFeature::kFencedframe},
+          {WebFeature::kHTMLUnsafeMethods, WebDXFeature::kParseHtmlUnsafe},
+          {WebFeature::kCloseWatcherScriptConstructor,
+           WebDXFeature::kClosewatcher},
+          {WebFeature::kHTMLSearchElement, WebDXFeature::kSearch},
+          {WebFeature::kDialogElement, WebDXFeature::kDialog},
+          {WebFeature::kV8DocumentPictureInPicture_RequestWindow_Method,
+           WebDXFeature::kDocumentPictureInPicture},
+          {WebFeature::kFlexGapSpecified, WebDXFeature::kFlexboxGap},
+          {WebFeature::kCSSFlexibleBox, WebDXFeature::kFlexbox},
+          {WebFeature::kCSSSelectorPseudoFocusVisible,
+           WebDXFeature::kFocusVisible},
+          {WebFeature::kCSSGridLayout, WebDXFeature::kGrid},
+          {WebFeature::kCSSSelectorPseudoHas, WebDXFeature::kHas},
+          {WebFeature::kCSSSelectorPseudoHasSlotted, WebDXFeature::kHasSlotted},
+          {WebFeature::kIdleDetectionStart, WebDXFeature::kIdleDetection},
+          {WebFeature::kImportMap, WebDXFeature::kImportMaps},
+          {WebFeature::kIntersectionObserverV2,
+           WebDXFeature::kIntersectionObserverV2},
+          {WebFeature::kIntersectionObserver_Constructor,
+           WebDXFeature::kIntersectionObserver},
+          {WebFeature::kCSSSelectorPseudoIs, WebDXFeature::kIs},
+          {WebFeature::kPrepareModuleScript, WebDXFeature::kJsModules},
+          {WebFeature::kInstantiateModuleScript, WebDXFeature::kJsModules},
+          {WebFeature::kV8MediaSession_Metadata_AttributeSetter,
+           WebDXFeature::kMediaSession},
+          {WebFeature::kOffscreenCanvas, WebDXFeature::kOffscreenCanvas},
+          {WebFeature::kV8StorageManager_GetDirectory_Method,
+           WebDXFeature::kOriginPrivateFileSystem},
+          {WebFeature::kV8Window_FetchLater_Method, WebDXFeature::kFetchlater},
+          {WebFeature::kV8Window_ShowOpenFilePicker_Method,
+           WebDXFeature::kFileSystemAccess},
+          {WebFeature::kV8Window_ShowSaveFilePicker_Method,
+           WebDXFeature::kFileSystemAccess},
+          {WebFeature::kV8Window_ShowDirectoryPicker_Method,
+           WebDXFeature::kFileSystemAccess},
+          {WebFeature::kV8HTMLVideoElement_RequestPictureInPicture_Method,
+           WebDXFeature::kPictureInPicture},
+          {WebFeature::kV8HTMLVideoElement_GetVideoPlaybackQuality_Method,
+           WebDXFeature::kMediaPlaybackQuality},
+          {WebFeature::kV8Window_QueryLocalFonts_Method,
+           WebDXFeature::kLocalFonts},
+          {WebFeature::kElementRequestPointerLock, WebDXFeature::kPointerLock},
+          {WebFeature::kCSSRelativeColor, WebDXFeature::kRelativeColor},
+          {WebFeature::kCSSAtRuleScope, WebDXFeature::kScope},
+          {WebFeature::kScrollend, WebDXFeature::kScrollend},
+          {WebFeature::kTextFragmentAnchor,
+           WebDXFeature::kScrollToTextFragment},
+          {WebFeature::kV8HTMLInputElement_ShowPicker_Method,
+           WebDXFeature::kShowPickerInput},
+          {WebFeature::kHTMLSlotElement, WebDXFeature::kSlot},
+          {WebFeature::kV8SpeechRecognition_Start_Method,
+           WebDXFeature::kSpeechRecognition},
+          {WebFeature::kV8SpeechSynthesis_Speak_Method,
+           WebDXFeature::kSpeechSynthesis},
+          {WebFeature::kStorageAccessAPI_HasStorageAccess_Method,
+           WebDXFeature::kStorageAccess},
+          {WebFeature::kStorageAccessAPI_requestStorageAccess_Method,
+           WebDXFeature::kStorageAccess},
+          {WebFeature::kStorageAccessAPI_requestStorageAccessFor_Method,
+           WebDXFeature::kStorageAccess},
+          {WebFeature::kStorageAccessAPI_requestStorageAccess_BeyondCookies,
+           WebDXFeature::kNonCookieStorageAccess},
+          {WebFeature::kStorageAccessAPI_hasUnpartitionedCookieAccess,
+           WebDXFeature::kNonCookieStorageAccess},
+          {WebFeature::kStorageBucketsOpen, WebDXFeature::kStorageBuckets},
+          {WebFeature::kCSSSelectorTargetText, WebDXFeature::kTargetText},
+          {WebFeature::kHTMLTemplateElement, WebDXFeature::kTemplate},
+          {WebFeature::kTextWrapBalance, WebDXFeature::kTextWrapBalance},
+          {WebFeature::kTextWrapPretty, WebDXFeature::kTextWrapPretty},
+          {WebFeature::kCSSSelectorUserValid, WebDXFeature::kUserPseudos},
+          {WebFeature::kCSSSelectorUserInvalid, WebDXFeature::kUserPseudos},
+          {WebFeature::kWebCodecs, WebDXFeature::kWebcodecs},
+          {WebFeature::kHidDeviceOpen, WebDXFeature::kWebhid},
+          {WebFeature::kV8LockManager_Request_Method, WebDXFeature::kWebLocks},
+          {WebFeature::kWebPImage, WebDXFeature::kWebp},
+          {WebFeature::kWebTransport, WebDXFeature::kWebtransport},
+          {WebFeature::kUsbDeviceOpen, WebDXFeature::kWebusb},
+          {WebFeature::kVTTCue, WebDXFeature::kWebvtt},
+          {WebFeature::kCSSSelectorPseudoWhere, WebDXFeature::kWhere},
+          {WebFeature::kDataListElement, WebDXFeature::kDatalist},
+          {WebFeature::kCSSSelectorPseudoDir, WebDXFeature::kDirPseudo},
+          {WebFeature::kHiddenUntilFoundAttribute,
+           WebDXFeature::kHiddenUntilFound},
+          {WebFeature::kAbortSignalAny, WebDXFeature::kAbortsignalAny},
+          {WebFeature::kAttributionReportingAPIAll,
+           WebDXFeature::kAttributionReporting},
+          {WebFeature::kNavigationAPI, WebDXFeature::kNavigation},
+          {WebFeature::kMathMLMathElement, WebDXFeature::kMathml},
+          {WebFeature::kCanvasRenderingContext2DConicGradient,
+           WebDXFeature::kCanvasCreateconicgradient},
+          {WebFeature::kCanvasRenderingContext2DReset,
+           WebDXFeature::kCanvasReset},
+          {WebFeature::kCanvasRenderingContext2DRoundRect,
+           WebDXFeature::kCanvasRoundrect},
+          {WebFeature::kCookieStoreAPI, WebDXFeature::kCookieStore},
+          {WebFeature::kCSSColorMixFunction, WebDXFeature::kColorMix},
+          {WebFeature::kImageSet, WebDXFeature::kImageSet},
+          {WebFeature::kStructuredCloneMethod, WebDXFeature::kStructuredClone},
+          {WebFeature::kSlotAssignNode, WebDXFeature::kSlotAssign},
+          {WebFeature::kDeviceMotionSecureOrigin,
+           WebDXFeature::kDeviceOrientationEvents},
+          {WebFeature::kDeviceOrientationAbsoluteSecureOrigin,
+           WebDXFeature::kDeviceOrientationEvents},
+          {WebFeature::kDeviceOrientationSecureOrigin,
+           WebDXFeature::kDeviceOrientationEvents},
+          {WebFeature::kGamepadButtons, WebDXFeature::kGamepad},
+          {WebFeature::kWakeLockAcquireScreenLock,
+           WebDXFeature::kScreenWakeLock},
+          {WebFeature::kWakeLockAcquireSystemLock,
+           WebDXFeature::kScreenWakeLock},
+          {WebFeature::kWebBluetoothRemoteServerConnect,
+           WebDXFeature::kWebBluetooth},
+          {WebFeature::kWebNfcNdefReaderScan, WebDXFeature::kWebNfc},
+          {WebFeature::kWebGPURequestAdapter, WebDXFeature::kWebgpu},
+          {WebFeature::kSerialPortOpen, WebDXFeature::kSerial},
+          {WebFeature::kModuleDedicatedWorker, WebDXFeature::kJsModulesWorkers},
+          {WebFeature::kModuleSharedWorker,
+           WebDXFeature::kJsModulesSharedWorkers},
+          {WebFeature::kCssDisplayPropertyMultipleValues,
+           WebDXFeature::kTwoValueDisplay},
+          {WebFeature::kTwoValuedOverflow, WebDXFeature::kOverflowShorthand},
+          {WebFeature::kKeyboardApiGetLayoutMap, WebDXFeature::kKeyboardMap},
+          {WebFeature::kSchedulerPostTask, WebDXFeature::kScheduler},
+          {WebFeature::kSchedulerYield, WebDXFeature::kScheduler},
+          {WebFeature::kTaskControllerConstructor, WebDXFeature::kScheduler},
+          {WebFeature::kTaskControllerSetPriority, WebDXFeature::kScheduler},
+          {WebFeature::kTaskSignalPriority, WebDXFeature::kScheduler},
+          {WebFeature::kKeyboardApiLock, WebDXFeature::kKeyboardLock},
+          {WebFeature::kAsyncClipboardAPIRead, WebDXFeature::kAsyncClipboard},
+          {WebFeature::kAsyncClipboardAPIWrite, WebDXFeature::kAsyncClipboard},
+          {WebFeature::kAsyncClipboardAPIReadText,
+           WebDXFeature::kAsyncClipboard},
+          {WebFeature::kAsyncClipboardAPIWriteText,
+           WebDXFeature::kAsyncClipboard},
+          {WebFeature::kHtmlClipboardApiUnsanitizedRead,
+           WebDXFeature::kClipboardUnsanitizedFormats},
+          {WebFeature::kClipboardCustomFormatRead,
+           WebDXFeature::kClipboardCustomFormat},
+          {WebFeature::kClipboardCustomFormatWrite,
+           WebDXFeature::kClipboardCustomFormat},
+          {WebFeature::kClipboardSvgRead, WebDXFeature::kClipboardSvg},
+          {WebFeature::kClipboardSvgWrite, WebDXFeature::kClipboardSvg},
+          {WebFeature::kV8ClipboardItem_Supports_Method,
+           WebDXFeature::kClipboardSupports},
+          {WebFeature::kV8AbortController_Constructor, WebDXFeature::kAborting},
+          {WebFeature::kV8AbortSignal_Abort_Method, WebDXFeature::kAborting},
+          {WebFeature::kAbortSignalTimeout, WebDXFeature::kAborting},
+          {WebFeature::kEditContext, WebDXFeature::kEditContext},
+          {WebFeature::kInertAttribute, WebDXFeature::kInert},
+          {WebFeature::kSegmenter, WebDXFeature::kIntlSegmenter},
+          {WebFeature::kEyeDropperOpen, WebDXFeature::kEyedropper},
+          {WebFeature::kElementCheckVisibility, WebDXFeature::kCheckVisibility},
+          {WebFeature::kElementGetHTML, WebDXFeature::kGethtml},
+          {WebFeature::kHTMLCanvasElement, WebDXFeature::kCanvas},
+          {WebFeature::kCanvasUseColorSpace,
+           WebDXFeature::kCanvas2dColorManagement},
+          {WebFeature::kCSSAtRuleStartingStyle, WebDXFeature::kStartingStyle},
+          {WebFeature::kCaretPositionFromPoint,
+           WebDXFeature::kDocumentCaretpositionfrompoint},
+          {WebFeature::kMeasureMemory, WebDXFeature::kMeasureMemory},
+          {WebFeature::kMediaQueryRangeSyntax,
+           WebDXFeature::kMediaQueryRangeSyntax},
+          {WebFeature::kCSSRegisterProperty,
+           WebDXFeature::kRegisteredCustomProperties},
+          {WebFeature::kCSSAtRuleProperty,
+           WebDXFeature::kRegisteredCustomProperties},
+          {WebFeature::kAccelerometerConstructor, WebDXFeature::kAccelerometer},
+          {WebFeature::kLinearAccelerationSensorConstructor,
+           WebDXFeature::kAccelerometer},
+          {WebFeature::kGravitySensorConstructor, WebDXFeature::kAccelerometer},
+          {WebFeature::kV8AudioContext_Constructor, WebDXFeature::kWebAudio},
+          {WebFeature::kV8OfflineAudioContext_Constructor,
+           WebDXFeature::kOfflineAudioContext},
+          {WebFeature::kAudioWorkletNodeConstructor,
+           WebDXFeature::kAudioWorklet},
+          {WebFeature::kMIDIPortOpen, WebDXFeature::kWebMidi},
+          {WebFeature::kV8HTMLVideoElement_RequestVideoFrameCallback_Method,
+           WebDXFeature::kRequestVideoFrameCallback},
+          {WebFeature::kSvgContextFillOrStroke,
+           WebDXFeature::kContextFillStroke},
+          {WebFeature::kHasCapUnits, WebDXFeature::kCap},
+          {WebFeature::kHasRcapUnits, WebDXFeature::kRcap},
+          {WebFeature::kHasIcUnits, WebDXFeature::kIc},
+          {WebFeature::kHasRicUnits, WebDXFeature::kRic},
+          {WebFeature::kHasLhUnits, WebDXFeature::kLh},
+          {WebFeature::kHasRlhUnits, WebDXFeature::kRlh},
+          {WebFeature::kCSSNesting, WebDXFeature::kNesting},
+          {WebFeature::kCSSSelectorNthChildOfSelector,
+           WebDXFeature::kNthChildOf},
+          {WebFeature::kForcedColorsMediaFeature, WebDXFeature::kForcedColors},
+          {WebFeature::kCSSTrigFunctions, WebDXFeature::kTrigFunctions},
+          {WebFeature::kCSSColorGradientColorSpace,
+           WebDXFeature::kGradientInterpolation},
+          {WebFeature::kCSSCalcConstants, WebDXFeature::kCalcConstants},
+          {WebFeature::kHasChUnits, WebDXFeature::kCh},
+          {WebFeature::kHasRchUnits, WebDXFeature::kRch},
+          {WebFeature::kHasRexUnits, WebDXFeature::kRex},
+          {WebFeature::kCSSColorFunction, WebDXFeature::kColorFunction},
+          {WebFeature::kCSSColor_SpaceOkLxx, WebDXFeature::kOklab},
+          {WebFeature::kCSSColor_SpaceLxx, WebDXFeature::kLab},
+          {WebFeature::kCSSColor_SpaceHwb, WebDXFeature::kHwb},
+          {WebFeature::kCSSKeywordRevert, WebDXFeature::kRevertValue},
+          {WebFeature::kPrefersContrastMediaFeature,
+           WebDXFeature::kPrefersContrast},
+          {WebFeature::kPrefersReducedTransparencyMediaFeature,
+           WebDXFeature::kPrefersReducedTransparency},
+          {WebFeature::kCSSRoundModRemFunctions, WebDXFeature::kRoundModRem},
+          {WebFeature::kCSSExponentialFunctions, WebDXFeature::kExpFunctions},
+          {WebFeature::kCSSLinearEasing, WebDXFeature::kLinearEasing},
+          {WebFeature::kOverflowMediaQuery, WebDXFeature::kOverflow},
+          {WebFeature::kHasSpellingOrGrammarErrorPseudoElement,
+           WebDXFeature::kSpellingGrammarError},
+          {WebFeature::kDynamicRangeMediaQuery, WebDXFeature::kDynamicRange},
+          {WebFeature::kHasMarkerPseudoElement, WebDXFeature::kMarker},
+          {WebFeature::kDisplayModeMediaQuery, WebDXFeature::kDisplayMode},
+          {WebFeature::kCSSWordBreakAutoPhrase,
+           WebDXFeature::kWordBreakAutoPhrase},
+          {WebFeature::kCSSSelectorPseudoModal, WebDXFeature::kModal},
+          {WebFeature::kUpdateMediaQuery, WebDXFeature::kUpdate},
+          {WebFeature::kCSSSelectorPseudoFileSelectorButton,
+           WebDXFeature::kFileSelectorButton},
+          {WebFeature::kClipPathGeometryBox, WebDXFeature::kClipPathBoxes},
+          {WebFeature::kActiveViewTransitionPseudo,
+           WebDXFeature::kActiveViewTransition},
+          {WebFeature::kSnapEvent, WebDXFeature::kScrollSnapEvents},
+          {WebFeature::kScrollTimelineConstructor,
+           WebDXFeature::kScrollDrivenAnimations},
+          {WebFeature::kViewTimelineConstructor,
+           WebDXFeature::kScrollDrivenAnimations},
+          {WebFeature::kScrollFunctionTimeline,
+           WebDXFeature::kScrollDrivenAnimations},
+          {WebFeature::kViewFunctionTimeline,
+           WebDXFeature::kScrollDrivenAnimations},
+          {WebFeature::kHighlightAPIRegisterHighlight,
+           WebDXFeature::kHighlight},
+          {WebFeature::kV8Element_Animate_Method, WebDXFeature::kWebAnimations},
+          {WebFeature::kV8Document_GetAnimations_Method,
+           WebDXFeature::kWebAnimations},
+          {WebFeature::kV8ShadowRoot_GetAnimations_Method,
+           WebDXFeature::kWebAnimations},
+          {WebFeature::kV8Element_GetAnimations_Method,
+           WebDXFeature::kWebAnimations},
+          {WebFeature::kV8Animation_Constructor, WebDXFeature::kWebAnimations},
+          {WebFeature::kCSSAtRuleViewTransition,
+           WebDXFeature::kCrossDocumentViewTransitions},
+          {WebFeature::kCSSCalcSizeFunction, WebDXFeature::kCalcSize},
+          {WebFeature::kLongAnimationFrameObserver,
+           WebDXFeature::kLongAnimationFrames},
+          {WebFeature::kLongAnimationFrameRequested,
+           WebDXFeature::kLongAnimationFrames},
+          {WebFeature::kVisibilityStateObserver,
+           WebDXFeature::kPageVisibilityState},
+          {WebFeature::kFedCm, WebDXFeature::kFedcm},
+          {WebFeature::kWebOTP, WebDXFeature::kWebOtp},
+          {WebFeature::kPreservesPitch, WebDXFeature::kPreservesPitch},
+          {WebFeature::kV8HTMLMediaElement_Remote_AttributeGetter,
+           WebDXFeature::kRemotePlayback},
+          {WebFeature::kVerticalFormControls,
+           WebDXFeature::kVerticalFormControls},
+          {WebFeature::kNavigatorPdfViewerEnabled, WebDXFeature::kPdfViewer},
+          {WebFeature::kReferrerPolicyHeader, WebDXFeature::kReferrerPolicy},
+          {WebFeature::kHTMLAnchorElementReferrerPolicyAttribute,
+           WebDXFeature::kReferrerPolicy},
+          {WebFeature::kHTMLElementWritingSuggestions,
+           WebDXFeature::kWritingsuggestions},
+          {WebFeature::kHTMLIFrameElementReferrerPolicyAttribute,
+           WebDXFeature::kReferrerPolicy},
+          {WebFeature::kHTMLImageElementReferrerPolicyAttribute,
+           WebDXFeature::kReferrerPolicy},
+          {WebFeature::kHTMLLinkElementReferrerPolicyAttribute,
+           WebDXFeature::kReferrerPolicy},
+          {WebFeature::kV8Ink_RequestPresenter_Method, WebDXFeature::kInk},
+          {WebFeature::kVirtualKeyboardHide, WebDXFeature::kVirtualKeyboard},
+          {WebFeature::kVirtualKeyboardOverlayPolicy,
+           WebDXFeature::kVirtualKeyboard},
+          {WebFeature::kVirtualKeyboardShow, WebDXFeature::kVirtualKeyboard},
+          {WebFeature::kDurationFormat, WebDXFeature::kIntlDurationFormat},
+          {WebFeature::kWebAppWindowControlsOverlay,
+           WebDXFeature::kWindowControlsOverlay},
+          {WebFeature::kPriorityHints, WebDXFeature::kFetchPriority},
+          {WebFeature::kBarPropLocationbar, WebDXFeature::kBarprop},
+          {WebFeature::kBarPropMenubar, WebDXFeature::kBarprop},
+          {WebFeature::kBarPropPersonalbar, WebDXFeature::kBarprop},
+          {WebFeature::kBarPropScrollbars, WebDXFeature::kBarprop},
+          {WebFeature::kBarPropStatusbar, WebDXFeature::kBarprop},
+          {WebFeature::kBarPropToolbar, WebDXFeature::kBarprop},
+          {WebFeature::kDocumentBeforeUnloadRegistered,
+           WebDXFeature::kBeforeunload},
+          {WebFeature::kLayoutShiftExplicitlyRequested,
+           WebDXFeature::kLayoutInstability},
+          {WebFeature::kJSSelfProfiling, WebDXFeature::kProfiler},
+          {WebFeature::kZstdContentEncoding, WebDXFeature::kZstd},
+          {WebFeature::kColumnPseudoElement, WebDXFeature::kColumnPseudo},
+          {WebFeature::kScrollButtonPseudoElement,
+           WebDXFeature::kScrollButtons},
+          {WebFeature::kScrollMarkerPseudoElement,
+           WebDXFeature::kScrollMarkers},
+          {WebFeature::kPopoverTypeHint, WebDXFeature::kPopoverHint},
+          {WebFeature::kFileHandlingLaunch, WebDXFeature::kAppFileHandlers},
+          {WebFeature::kWebAppManifestProtocolHandlers,
+           WebDXFeature::kAppProtocolHandlers},
+          {WebFeature::kBadgeSetWithoutNotificationPermissionInBrowserWindow,
+           WebDXFeature::kBadging},
+          {WebFeature::kBadgeSetWithoutNotificationPermissionInAppWindow,
+           WebDXFeature::kBadging},
+          {WebFeature::kBadgeSetWithoutNotificationPermissionInWorker,
+           WebDXFeature::kBadging},
+          {WebFeature::kBeforeInstallPromptEvent,
+           WebDXFeature::kBeforeinstallprompt},
+          {WebFeature::kBeforeInstallPromptEventUserChoice,
+           WebDXFeature::kBeforeinstallprompt},
+          {WebFeature::kBeforeInstallPromptEventPreventDefault,
+           WebDXFeature::kBeforeinstallprompt},
+          {WebFeature::kBeforeInstallPromptEventPrompt,
+           WebDXFeature::kBeforeinstallprompt},
+          {WebFeature::kV8BeforeInstallPromptEvent_Platforms_AttributeGetter,
+           WebDXFeature::kBeforeinstallprompt},
+          {WebFeature::kWebAppManifestLaunchHandler,
+           WebDXFeature::kAppLaunchHandler},
+          {WebFeature::kCSSFunctions, WebDXFeature::kFunction},
+          {WebFeature::kSelectionGetComposedRanges,
+           WebDXFeature::kComposedRanges},
+          {WebFeature::kSharedDictionaryUsed,
+           WebDXFeature::kCompressionDictionaryTransport},
+          {WebFeature::kHttp3, WebDXFeature::kHttp3},
+          {WebFeature::kSpeculationRules, WebDXFeature::kSpeculationRules},
+          {WebFeature::kElementCapture, WebDXFeature::kElementCapture},
+          {WebFeature::kRegionCapture, WebDXFeature::kRegionCapture},
+          // Add new features above this line.
+      }};
 
   return *kMap;
 }
@@ -395,139 +392,136 @@
       WebDXFeature::kLogicalProperties;
   static const base::NoDestructor<
       const base::flat_map<CSSSampleId, WebDXFeature>>
-      kMap{{{CSSSampleId::kAccentColor, WebDXFeature::kAccentColor},
-            {CSSSampleId::kAnchorName, WebDXFeature::kAnchorPositioning},
-            {CSSSampleId::kAnimationComposition,
-             WebDXFeature::kAnimationComposition},
-            {CSSSampleId::kAppearance, WebDXFeature::kAppearance},
-            {CSSSampleId::kAspectRatio, WebDXFeature::kAspectRatio},
-            {CSSSampleId::kBackdropFilter, WebDXFeature::kBackdropFilter},
-            {CSSSampleId::kBorderImage, WebDXFeature::kBorderImage},
-            {CSSSampleId::kColorScheme, WebDXFeature::kColorScheme},
-            {CSSSampleId::kContainIntrinsicSize,
-             WebDXFeature::kContainIntrinsicSize},
-            {CSSSampleId::kFieldSizing, WebDXFeature::kFieldSizing},
-            {CSSSampleId::kFontOpticalSizing, WebDXFeature::kFontOpticalSizing},
-            {CSSSampleId::kFontPalette, WebDXFeature::kFontPalette},
-            {CSSSampleId::kFontSizeAdjust, WebDXFeature::kFontSizeAdjust},
-            {CSSSampleId::kFontSynthesisSmallCaps,
-             WebDXFeature::kFontSynthesisSmallCaps},
-            {CSSSampleId::kFontSynthesisStyle,
-             WebDXFeature::kFontSynthesisStyle},
-            {CSSSampleId::kFontSynthesisWeight,
-             WebDXFeature::kFontSynthesisWeight},
-            {CSSSampleId::kFontSynthesis, WebDXFeature::kFontSynthesis},
-            {CSSSampleId::kFontVariantAlternates,
-             WebDXFeature::kFontVariantAlternates},
-            {CSSSampleId::kFontVariantEmoji, WebDXFeature::kFontVariantEmoji},
-            {CSSSampleId::kHyphens, WebDXFeature::kHyphens},
-            {CSSSampleId::kScrollbarColor, WebDXFeature::kScrollbarColor},
-            {CSSSampleId::kScrollbarGutter, WebDXFeature::kScrollbarGutter},
-            {CSSSampleId::kScrollbarWidth, WebDXFeature::kScrollbarWidth},
-            {CSSSampleId::kScrollSnapType, WebDXFeature::kScrollSnap},
-            {CSSSampleId::kScrollInitialTarget,
-             WebDXFeature::kScrollInitialTarget},
-            {CSSSampleId::kTextBox, WebDXFeature::kTextBox},
-            {CSSSampleId::kTextBoxEdge, WebDXFeature::kTextBox},
-            {CSSSampleId::kTextBoxTrim, WebDXFeature::kTextBox},
-            {CSSSampleId::kTextIndent, WebDXFeature::kTextIndent},
-            {CSSSampleId::kTextSpacingTrim, WebDXFeature::kTextSpacingTrim},
-            {CSSSampleId::kTransitionBehavior,
-             WebDXFeature::kTransitionBehavior},
-            {CSSSampleId::kTranslate, WebDXFeature::kIndividualTransforms},
-            {CSSSampleId::kRotate, WebDXFeature::kIndividualTransforms},
-            {CSSSampleId::kScale, WebDXFeature::kIndividualTransforms},
-            {CSSSampleId::kWillChange, WebDXFeature::kWillChange},
-            {CSSSampleId::kMaskImage, WebDXFeature::kMasks},
-            {CSSSampleId::kMaskClip, WebDXFeature::kMasks},
-            {CSSSampleId::kMaskSize, WebDXFeature::kMasks},
-            {CSSSampleId::kMaskOrigin, WebDXFeature::kMasks},
-            {CSSSampleId::kMaskRepeat, WebDXFeature::kMasks},
-            {CSSSampleId::kMaskComposite, WebDXFeature::kMasks},
-            {CSSSampleId::kMaskPosition, WebDXFeature::kMasks},
-            {CSSSampleId::kMaskMode, WebDXFeature::kMasks},
-            {CSSSampleId::kMask, WebDXFeature::kMasks},
-            {CSSSampleId::kPaintOrder, WebDXFeature::kPaintOrder},
-            {CSSSampleId::kBackgroundClip, WebDXFeature::kBackgroundClipText},
-            {CSSSampleId::kContainIntrinsicInlineSize,
-             WebDXFeature::kContainInlineSize},
-            {CSSSampleId::kOverlay, WebDXFeature::kOverlay},
-            {CSSSampleId::kContentVisibility, WebDXFeature::kContentVisibility},
-            {CSSSampleId::kCounterSet, WebDXFeature::kCounterSet},
-            {CSSSampleId::kObjectViewBox, WebDXFeature::kObjectViewBox},
-            {CSSSampleId::kForcedColorAdjust, WebDXFeature::kForcedColors},
-            {CSSSampleId::kWhiteSpaceCollapse,
-             WebDXFeature::kWhiteSpaceCollapse},
-            {CSSSampleId::kImageOrientation, WebDXFeature::kImageOrientation},
-            // https://drafts.csswg.org/css-logical-1/
-            {CSSSampleId::kBlockSize, logical_properties},
-            {CSSSampleId::kInlineSize, logical_properties},
-            {CSSSampleId::kMinBlockSize, logical_properties},
-            {CSSSampleId::kMaxBlockSize, logical_properties},
-            {CSSSampleId::kMinInlineSize, logical_properties},
-            {CSSSampleId::kMaxInlineSize, logical_properties},
-            {CSSSampleId::kMarginBlockStart, logical_properties},
-            {CSSSampleId::kMarginBlockEnd, logical_properties},
-            {CSSSampleId::kMarginInlineStart, logical_properties},
-            {CSSSampleId::kMarginInlineEnd, logical_properties},
-            {CSSSampleId::kInsetBlockStart, logical_properties},
-            {CSSSampleId::kInsetBlockEnd, logical_properties},
-            {CSSSampleId::kInsetInlineStart, logical_properties},
-            {CSSSampleId::kInsetInlineEnd, logical_properties},
-            {CSSSampleId::kPaddingBlockStart, logical_properties},
-            {CSSSampleId::kPaddingBlockEnd, logical_properties},
-            {CSSSampleId::kPaddingInlineStart, logical_properties},
-            {CSSSampleId::kPaddingInlineEnd, logical_properties},
-            {CSSSampleId::kBorderBlockStartWidth, logical_properties},
-            {CSSSampleId::kBorderBlockEndWidth, logical_properties},
-            {CSSSampleId::kBorderInlineStartWidth, logical_properties},
-            {CSSSampleId::kBorderInlineEndWidth, logical_properties},
-            {CSSSampleId::kBorderBlockStartStyle, logical_properties},
-            {CSSSampleId::kBorderBlockEndStyle, logical_properties},
-            {CSSSampleId::kBorderInlineStartStyle, logical_properties},
-            {CSSSampleId::kBorderInlineEndStyle, logical_properties},
-            {CSSSampleId::kBorderBlockStartColor, logical_properties},
-            {CSSSampleId::kBorderBlockEndColor, logical_properties},
-            {CSSSampleId::kBorderInlineStartColor, logical_properties},
-            {CSSSampleId::kBorderInlineEndColor, logical_properties},
-            {CSSSampleId::kBorderStartStartRadius, logical_properties},
-            {CSSSampleId::kBorderEndEndRadius, logical_properties},
-            {CSSSampleId::kBorderStartEndRadius, logical_properties},
-            {CSSSampleId::kBorderEndStartRadius, logical_properties},
-            {CSSSampleId::kFontVariantPosition,
-             WebDXFeature::kFontVariantPosition},
-            {CSSSampleId::kTextEmphasis, WebDXFeature::kTextEmphasis},
-            {CSSSampleId::kTextWrap, WebDXFeature::kTextWrap},
-            {CSSSampleId::kTextWrapStyle, WebDXFeature::kTextWrapStyle},
-            {CSSSampleId::kOffsetPath, WebDXFeature::kMotionPath},
-            {CSSSampleId::kOutline, WebDXFeature::kOutline},
-            {CSSSampleId::kTransformBox, WebDXFeature::kTransformBox},
-            {CSSSampleId::kRubyAlign, WebDXFeature::kRubyAlign},
-            {CSSSampleId::kRubyPosition, WebDXFeature::kRubyPosition},
-            {CSSSampleId::kInitialLetter, WebDXFeature::kInitialLetter},
-            {CSSSampleId::kHyphenateCharacter,
-             WebDXFeature::kHyphenateCharacter},
-            {CSSSampleId::kHyphenateLimitChars,
-             WebDXFeature::kHyphenateLimitChars},
-            {CSSSampleId::kQuotes, WebDXFeature::kQuotes},
-            {CSSSampleId::kBaselineSource, WebDXFeature::kBaselineSource},
-            {CSSSampleId::kClipPath, WebDXFeature::kClipPath},
-            {CSSSampleId::kBoxDecorationBreak,
-             WebDXFeature::kBoxDecorationBreak},
-            {CSSSampleId::kPageOrientation, WebDXFeature::kPageOrientation},
-            {CSSSampleId::kScrollTimeline,
-             WebDXFeature::kScrollDrivenAnimations},
-            {CSSSampleId::kScrollTimelineName,
-             WebDXFeature::kScrollDrivenAnimations},
-            {CSSSampleId::kViewTimeline, WebDXFeature::kScrollDrivenAnimations},
-            {CSSSampleId::kViewTimelineName,
-             WebDXFeature::kScrollDrivenAnimations},
-            {CSSSampleId::kInterpolateSize, WebDXFeature::kInterpolateSize},
-            {CSSSampleId::kViewTransitionClass,
-             WebDXFeature::kViewTransitionClass},
-            {CSSSampleId::kScrollMarkerGroup, WebDXFeature::kScrollMarkers},
-            {CSSSampleId::kInteractivity, WebDXFeature::kInteractivity},
-            {CSSSampleId::kReadingFlow, WebDXFeature::kReadingFlow}}};
+      kMap{{
+          {CSSSampleId::kAccentColor, WebDXFeature::kAccentColor},
+          {CSSSampleId::kAnchorName, WebDXFeature::kAnchorPositioning},
+          {CSSSampleId::kAnimationComposition,
+           WebDXFeature::kAnimationComposition},
+          {CSSSampleId::kAppearance, WebDXFeature::kAppearance},
+          {CSSSampleId::kAspectRatio, WebDXFeature::kAspectRatio},
+          {CSSSampleId::kBackdropFilter, WebDXFeature::kBackdropFilter},
+          {CSSSampleId::kBorderImage, WebDXFeature::kBorderImage},
+          {CSSSampleId::kColorScheme, WebDXFeature::kColorScheme},
+          {CSSSampleId::kContainIntrinsicSize,
+           WebDXFeature::kContainIntrinsicSize},
+          {CSSSampleId::kFieldSizing, WebDXFeature::kFieldSizing},
+          {CSSSampleId::kFontOpticalSizing, WebDXFeature::kFontOpticalSizing},
+          {CSSSampleId::kFontPalette, WebDXFeature::kFontPalette},
+          {CSSSampleId::kFontSizeAdjust, WebDXFeature::kFontSizeAdjust},
+          {CSSSampleId::kFontSynthesisSmallCaps,
+           WebDXFeature::kFontSynthesisSmallCaps},
+          {CSSSampleId::kFontSynthesisStyle, WebDXFeature::kFontSynthesisStyle},
+          {CSSSampleId::kFontSynthesisWeight,
+           WebDXFeature::kFontSynthesisWeight},
+          {CSSSampleId::kFontSynthesis, WebDXFeature::kFontSynthesis},
+          {CSSSampleId::kFontVariantAlternates,
+           WebDXFeature::kFontVariantAlternates},
+          {CSSSampleId::kFontVariantEmoji, WebDXFeature::kFontVariantEmoji},
+          {CSSSampleId::kHyphens, WebDXFeature::kHyphens},
+          {CSSSampleId::kScrollbarColor, WebDXFeature::kScrollbarColor},
+          {CSSSampleId::kScrollbarGutter, WebDXFeature::kScrollbarGutter},
+          {CSSSampleId::kScrollbarWidth, WebDXFeature::kScrollbarWidth},
+          {CSSSampleId::kScrollSnapType, WebDXFeature::kScrollSnap},
+          {CSSSampleId::kScrollInitialTarget,
+           WebDXFeature::kScrollInitialTarget},
+          {CSSSampleId::kTextBox, WebDXFeature::kTextBox},
+          {CSSSampleId::kTextBoxEdge, WebDXFeature::kTextBox},
+          {CSSSampleId::kTextBoxTrim, WebDXFeature::kTextBox},
+          {CSSSampleId::kTextIndent, WebDXFeature::kTextIndent},
+          {CSSSampleId::kTextSpacingTrim, WebDXFeature::kTextSpacingTrim},
+          {CSSSampleId::kTransitionBehavior, WebDXFeature::kTransitionBehavior},
+          {CSSSampleId::kTranslate, WebDXFeature::kIndividualTransforms},
+          {CSSSampleId::kRotate, WebDXFeature::kIndividualTransforms},
+          {CSSSampleId::kScale, WebDXFeature::kIndividualTransforms},
+          {CSSSampleId::kWillChange, WebDXFeature::kWillChange},
+          {CSSSampleId::kMaskImage, WebDXFeature::kMasks},
+          {CSSSampleId::kMaskClip, WebDXFeature::kMasks},
+          {CSSSampleId::kMaskSize, WebDXFeature::kMasks},
+          {CSSSampleId::kMaskOrigin, WebDXFeature::kMasks},
+          {CSSSampleId::kMaskRepeat, WebDXFeature::kMasks},
+          {CSSSampleId::kMaskComposite, WebDXFeature::kMasks},
+          {CSSSampleId::kMaskPosition, WebDXFeature::kMasks},
+          {CSSSampleId::kMaskMode, WebDXFeature::kMasks},
+          {CSSSampleId::kMask, WebDXFeature::kMasks},
+          {CSSSampleId::kPaintOrder, WebDXFeature::kPaintOrder},
+          {CSSSampleId::kBackgroundClip, WebDXFeature::kBackgroundClipText},
+          {CSSSampleId::kContainIntrinsicInlineSize,
+           WebDXFeature::kContainInlineSize},
+          {CSSSampleId::kOverlay, WebDXFeature::kOverlay},
+          {CSSSampleId::kContentVisibility, WebDXFeature::kContentVisibility},
+          {CSSSampleId::kCounterSet, WebDXFeature::kCounterSet},
+          {CSSSampleId::kObjectViewBox, WebDXFeature::kObjectViewBox},
+          {CSSSampleId::kForcedColorAdjust, WebDXFeature::kForcedColors},
+          {CSSSampleId::kWhiteSpaceCollapse, WebDXFeature::kWhiteSpaceCollapse},
+          {CSSSampleId::kImageOrientation, WebDXFeature::kImageOrientation},
+          // https://drafts.csswg.org/css-logical-1/
+          {CSSSampleId::kBlockSize, logical_properties},
+          {CSSSampleId::kInlineSize, logical_properties},
+          {CSSSampleId::kMinBlockSize, logical_properties},
+          {CSSSampleId::kMaxBlockSize, logical_properties},
+          {CSSSampleId::kMinInlineSize, logical_properties},
+          {CSSSampleId::kMaxInlineSize, logical_properties},
+          {CSSSampleId::kMarginBlockStart, logical_properties},
+          {CSSSampleId::kMarginBlockEnd, logical_properties},
+          {CSSSampleId::kMarginInlineStart, logical_properties},
+          {CSSSampleId::kMarginInlineEnd, logical_properties},
+          {CSSSampleId::kInsetBlockStart, logical_properties},
+          {CSSSampleId::kInsetBlockEnd, logical_properties},
+          {CSSSampleId::kInsetInlineStart, logical_properties},
+          {CSSSampleId::kInsetInlineEnd, logical_properties},
+          {CSSSampleId::kPaddingBlockStart, logical_properties},
+          {CSSSampleId::kPaddingBlockEnd, logical_properties},
+          {CSSSampleId::kPaddingInlineStart, logical_properties},
+          {CSSSampleId::kPaddingInlineEnd, logical_properties},
+          {CSSSampleId::kBorderBlockStartWidth, logical_properties},
+          {CSSSampleId::kBorderBlockEndWidth, logical_properties},
+          {CSSSampleId::kBorderInlineStartWidth, logical_properties},
+          {CSSSampleId::kBorderInlineEndWidth, logical_properties},
+          {CSSSampleId::kBorderBlockStartStyle, logical_properties},
+          {CSSSampleId::kBorderBlockEndStyle, logical_properties},
+          {CSSSampleId::kBorderInlineStartStyle, logical_properties},
+          {CSSSampleId::kBorderInlineEndStyle, logical_properties},
+          {CSSSampleId::kBorderBlockStartColor, logical_properties},
+          {CSSSampleId::kBorderBlockEndColor, logical_properties},
+          {CSSSampleId::kBorderInlineStartColor, logical_properties},
+          {CSSSampleId::kBorderInlineEndColor, logical_properties},
+          {CSSSampleId::kBorderStartStartRadius, logical_properties},
+          {CSSSampleId::kBorderEndEndRadius, logical_properties},
+          {CSSSampleId::kBorderStartEndRadius, logical_properties},
+          {CSSSampleId::kBorderEndStartRadius, logical_properties},
+          {CSSSampleId::kFontVariantPosition,
+           WebDXFeature::kFontVariantPosition},
+          {CSSSampleId::kTextEmphasis, WebDXFeature::kTextEmphasis},
+          {CSSSampleId::kTextWrap, WebDXFeature::kTextWrap},
+          {CSSSampleId::kTextWrapStyle, WebDXFeature::kTextWrapStyle},
+          {CSSSampleId::kOffsetPath, WebDXFeature::kMotionPath},
+          {CSSSampleId::kOutline, WebDXFeature::kOutline},
+          {CSSSampleId::kTransformBox, WebDXFeature::kTransformBox},
+          {CSSSampleId::kRubyAlign, WebDXFeature::kRubyAlign},
+          {CSSSampleId::kRubyPosition, WebDXFeature::kRubyPosition},
+          {CSSSampleId::kInitialLetter, WebDXFeature::kInitialLetter},
+          {CSSSampleId::kHyphenateCharacter, WebDXFeature::kHyphenateCharacter},
+          {CSSSampleId::kHyphenateLimitChars,
+           WebDXFeature::kHyphenateLimitChars},
+          {CSSSampleId::kQuotes, WebDXFeature::kQuotes},
+          {CSSSampleId::kBaselineSource, WebDXFeature::kBaselineSource},
+          {CSSSampleId::kClipPath, WebDXFeature::kClipPath},
+          {CSSSampleId::kBoxDecorationBreak, WebDXFeature::kBoxDecorationBreak},
+          {CSSSampleId::kPageOrientation, WebDXFeature::kPageOrientation},
+          {CSSSampleId::kScrollTimeline, WebDXFeature::kScrollDrivenAnimations},
+          {CSSSampleId::kScrollTimelineName,
+           WebDXFeature::kScrollDrivenAnimations},
+          {CSSSampleId::kViewTimeline, WebDXFeature::kScrollDrivenAnimations},
+          {CSSSampleId::kViewTimelineName,
+           WebDXFeature::kScrollDrivenAnimations},
+          {CSSSampleId::kInterpolateSize, WebDXFeature::kInterpolateSize},
+          {CSSSampleId::kViewTransitionClass,
+           WebDXFeature::kViewTransitionClass},
+          {CSSSampleId::kScrollMarkerGroup, WebDXFeature::kScrollMarkers},
+          {CSSSampleId::kInteractivity, WebDXFeature::kInteractivity},
+          {CSSSampleId::kReadingFlow, WebDXFeature::kReadingFlow},
+          // Add new features above this line.
+      }};
 
   return *kMap;
 }
@@ -542,6 +536,7 @@
           // else that matches the resolution of the investigation
           // {CSSSampleId::kFontPalette, WebDXFeature::kFontPaletteAnimation}
           {CSSSampleId::kDisplay, WebDXFeature::kDisplayAnimation},
+          // Add new features above this line.
       }};
 
   return *kMap;
diff --git a/components/policy/resources/templates/policy_definitions/ContentSettings/GetDisplayMediaSetSelectAllScreensAllowedForUrls.yaml b/components/policy/resources/templates/policy_definitions/ContentSettings/GetDisplayMediaSetSelectAllScreensAllowedForUrls.yaml
index f1fa0e9c5..359aafd 100644
--- a/components/policy/resources/templates/policy_definitions/ContentSettings/GetDisplayMediaSetSelectAllScreensAllowedForUrls.yaml
+++ b/components/policy/resources/templates/policy_definitions/ContentSettings/GetDisplayMediaSetSelectAllScreensAllowedForUrls.yaml
@@ -1,4 +1,5 @@
 caption: Enables auto-select for multi screen captures
+deprecated: true
 desc: |-
   The <ph name="GET_DISPLAY_MEDIA_SET_NAME">getDisplayMediaSet</ph> API allows web applications to capture multiple surfaces at once.
         This policy unlocks the <ph name="AUTO_SELECT_ALL_SCREENS_NAME">autoSelectAllScreens</ph> property for web applications at defined origins.
@@ -20,7 +21,7 @@
     type: string
   type: array
 supported_on:
-- chrome_os:102-
+- chrome_os:102-136
 - chrome.linux:111-123
 tags: []
 type: list
diff --git a/components/policy/test/data/pref_mapping/GetDisplayMediaSetSelectAllScreensAllowedForUrls.json b/components/policy/test/data/pref_mapping/GetDisplayMediaSetSelectAllScreensAllowedForUrls.json
deleted file mode 100644
index e0cee47..0000000
--- a/components/policy/test/data/pref_mapping/GetDisplayMediaSetSelectAllScreensAllowedForUrls.json
+++ /dev/null
@@ -1,16 +0,0 @@
-[
-  {
-    "os": [
-      "chromeos"
-    ],
-    "simple_policy_pref_mapping_test": {
-      "pref_name": "profile.managed_access_to_get_all_screens_media_allowed_for_urls",
-      "default_value": [],
-      "values_to_test": [
-        [
-          "[*.]google.com"
-        ]
-      ]
-    }
-  }
-]
diff --git a/components/sync/base/data_type.cc b/components/sync/base/data_type.cc
index 8f42e42..e495de4 100644
--- a/components/sync/base/data_type.cc
+++ b/components/sync/base/data_type.cc
@@ -9,6 +9,7 @@
 #include "base/containers/fixed_flat_map.h"
 #include "base/logging.h"
 #include "base/notreached.h"
+#include "components/sync/base/features.h"
 #include "components/sync/protocol/entity_specifics.pb.h"
 
 namespace syncer {
@@ -596,6 +597,21 @@
   return UNSPECIFIED;
 }
 
+DataTypeSet AlwaysPreferredUserTypes() {
+  DataTypeSet types = {DEVICE_INFO,          USER_CONSENTS,
+                       PLUS_ADDRESS,         PLUS_ADDRESS_SETTING,
+                       PRIORITY_PREFERENCES, SECURITY_EVENTS,
+                       SEND_TAB_TO_SELF,     SUPERVISED_USER_SETTINGS,
+                       SHARING_MESSAGE};
+  // TODO(crbug.com/412602018): Mark AlwaysPreferredUserTypes() method as
+  // constexpr when removing the feature flag.
+  if (!base::FeatureList::IsEnabled(
+          kSyncSupportAlwaysSyncingPriorityPreferences)) {
+    types.Remove(PRIORITY_PREFERENCES);
+  }
+  return types;
+}
+
 DataTypeSet EncryptableUserTypes() {
   static_assert(55 == syncer::GetNumDataTypes(),
                 "If adding an unencryptable type, remove from "
diff --git a/components/sync/base/data_type.h b/components/sync/base/data_type.h
index f332e09..63c3e3cf0 100644
--- a/components/sync/base/data_type.h
+++ b/components/sync/base/data_type.h
@@ -303,16 +303,7 @@
 }
 
 // User types which are not user-controlled.
-constexpr DataTypeSet AlwaysPreferredUserTypes() {
-  return {DEVICE_INFO,
-          USER_CONSENTS,
-          PLUS_ADDRESS,
-          PLUS_ADDRESS_SETTING,
-          SECURITY_EVENTS,
-          SEND_TAB_TO_SELF,
-          SUPERVISED_USER_SETTINGS,
-          SHARING_MESSAGE};
-}
+DataTypeSet AlwaysPreferredUserTypes();
 
 // User types which are always encrypted.
 constexpr DataTypeSet AlwaysEncryptedUserTypes() {
diff --git a/components/sync/base/features.cc b/components/sync/base/features.cc
index 98f696c9..3bca948f 100644
--- a/components/sync/base/features.cc
+++ b/components/sync/base/features.cc
@@ -78,6 +78,10 @@
 #endif
 );
 
+BASE_FEATURE(kSyncSupportAlwaysSyncingPriorityPreferences,
+             "SyncSupportAlwaysSyncingPriorityPreferences",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kEnableBookmarksSelectedTypeOnSigninForTesting,
              "EnableBookmarksSelectedTypeOnSigninForTesting",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/components/sync/base/features.h b/components/sync/base/features.h
index 181ceda..555bc78 100644
--- a/components/sync/base/features.h
+++ b/components/sync/base/features.h
@@ -72,6 +72,11 @@
 // Feature flag to replace all sync-related UI with sign-in ones.
 BASE_DECLARE_FEATURE(kReplaceSyncPromosWithSignInPromos);
 
+// If enabled, allowlisted priority preferences will be synced even if the
+// preferences user toggle is off. Note that this flag is only meaningful if
+// kEnablePreferencesAccountStorage is enabled.
+BASE_DECLARE_FEATURE(kSyncSupportAlwaysSyncingPriorityPreferences);
+
 // Normally, if kReplaceSyncPromosWithSignInPromos is disabled,
 // UserSelectableType::kBookmarks is disabled by default upon sign-in. This
 // flag makes the type enabled by default, for manual testing.
diff --git a/components/sync/base/user_selectable_type.cc b/components/sync/base/user_selectable_type.cc
index ae0deeca..5c699b9 100644
--- a/components/sync/base/user_selectable_type.cc
+++ b/components/sync/base/user_selectable_type.cc
@@ -7,8 +7,10 @@
 #include <optional>
 #include <ostream>
 
+#include "base/feature_list.h"
 #include "base/notreached.h"
 #include "components/sync/base/data_type.h"
+#include "components/sync/base/features.h"
 
 namespace syncer {
 
@@ -50,10 +52,14 @@
   switch (type) {
     case UserSelectableType::kBookmarks:
       return {kBookmarksTypeName, BOOKMARKS, {BOOKMARKS, POWER_BOOKMARK}};
-    case UserSelectableType::kPreferences:
-      return {kPreferencesTypeName,
-              PREFERENCES,
-              {PREFERENCES, DICTIONARY, PRIORITY_PREFERENCES, SEARCH_ENGINES}};
+    case UserSelectableType::kPreferences: {
+      DataTypeSet types = {PREFERENCES, DICTIONARY, SEARCH_ENGINES};
+      if (!base::FeatureList::IsEnabled(
+              kSyncSupportAlwaysSyncingPriorityPreferences)) {
+        types.Put(PRIORITY_PREFERENCES);
+      }
+      return {kPreferencesTypeName, PREFERENCES, types};
+    }
     case UserSelectableType::kPasswords:
       return {
           kPasswordsTypeName,
diff --git a/content/browser/attribution_reporting/aggregatable_attribution_utils.cc b/content/browser/attribution_reporting/aggregatable_attribution_utils.cc
index 0ad0bd6..5d2c2f4 100644
--- a/content/browser/attribution_reporting/aggregatable_attribution_utils.cc
+++ b/content/browser/attribution_reporting/aggregatable_attribution_utils.cc
@@ -167,9 +167,6 @@
       std::get_if<AttributionReport::AggregatableData>(&report.data());
   DCHECK(aggregatable_data);
 
-  std::vector<blink::mojom::AggregatableReportHistogramContribution>
-      contributions = aggregatable_data->contributions();
-
   const AttributionInfo& attribution_info = report.attribution_info();
 
   AggregatableReportSharedInfo::DebugMode debug_mode =
@@ -195,7 +192,7 @@
   return AggregatableReportRequest::Create(
       AggregationServicePayloadContents(
           AggregationServicePayloadContents::Operation::kHistogram,
-          std::move(contributions),
+          aggregatable_data->contributions(),
           blink::mojom::AggregationServiceMode::kDefault,
           aggregatable_data->aggregation_coordinator_origin()
               ? std::make_optional(
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager.h b/content/browser/attribution_reporting/attribution_data_host_manager.h
index e7fea8b5..8d104c9a 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager.h
+++ b/content/browser/attribution_reporting/attribution_data_host_manager.h
@@ -89,7 +89,7 @@
   virtual bool NotifyNavigationRegistrationData(
       const blink::AttributionSrcToken& attribution_src_token,
       const net::HttpResponseHeaders* headers,
-      GURL reporting_url) = 0;
+      const GURL& reporting_url) = 0;
 
   // Notifies the manager whenever an attribution-enabled navigation request
   // completes. Should be called even for navigations when
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
index b234227..3636af6 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.cc
@@ -1461,7 +1461,7 @@
 bool AttributionDataHostManagerImpl::NotifyNavigationRegistrationData(
     const blink::AttributionSrcToken& attribution_src_token,
     const net::HttpResponseHeaders* headers,
-    GURL reporting_url) {
+    const GURL& reporting_url) {
   auto reporting_origin = SuitableOrigin::Create(reporting_url);
   CHECK(reporting_origin);
 
@@ -1475,7 +1475,7 @@
   CHECK(!it->registrations_complete());
 
   auto pending_registration_data = PendingRegistrationData::Get(
-      headers, *it, std::move(reporting_url), *std::move(reporting_origin));
+      headers, *it, reporting_url, *std::move(reporting_origin));
 
   if (!pending_registration_data.has_value()) {
     return false;
diff --git a/content/browser/attribution_reporting/attribution_data_host_manager_impl.h b/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
index a91dee2b..6b39303a 100644
--- a/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_data_host_manager_impl.h
@@ -98,7 +98,7 @@
   bool NotifyNavigationRegistrationData(
       const blink::AttributionSrcToken& attribution_src_token,
       const net::HttpResponseHeaders* headers,
-      GURL reporting_url) override;
+      const GURL& reporting_url) override;
   void NotifyNavigationRegistrationCompleted(
       const blink::AttributionSrcToken& attribution_src_token) override;
 
diff --git a/content/browser/attribution_reporting/attribution_debug_report.cc b/content/browser/attribution_reporting/attribution_debug_report.cc
index 41c74fc7..53788de 100644
--- a/content/browser/attribution_reporting/attribution_debug_report.cc
+++ b/content/browser/attribution_reporting/attribution_debug_report.cc
@@ -71,16 +71,16 @@
 
 std::optional<DebugDataTypeAndBody> GetReportDataBody(
     const StoreSourceResult& result) {
-  base::Value::Dict additional_fields;
-  if (result.destination_limit().has_value()) {
-    additional_fields.Set("source_destination_limit",
-                          GetLimit(result.destination_limit().value()));
-  }
-
   const auto make_report_body = [&](DebugDataType type,
                                     base::Value limit = base::Value()) {
-    return std::make_optional(DebugDataTypeAndBody(
-        type, std::move(limit), std::move(additional_fields)));
+    base::Value::Dict additional_fields;
+    if (result.destination_limit().has_value()) {
+      additional_fields.Set("source_destination_limit",
+                            GetLimit(result.destination_limit().value()));
+    }
+
+    return std::make_optional<DebugDataTypeAndBody>(
+        type, std::move(limit), std::move(additional_fields));
   };
 
   return std::visit(
@@ -164,66 +164,66 @@
             return std::optional<DebugDataTypeAndBody>();
           },
           [](CreateReportResult::InternalError) {
-            return std::make_optional(
-                DebugDataTypeAndBody(DebugDataType::kTriggerUnknownError));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerUnknownError);
           },
           [](CreateReportResult::NoCapacityForConversionDestination v) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerEventStorageLimit, GetLimit(v.max)));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventStorageLimit, GetLimit(v.max));
           },
           [](CreateReportResult::ExcessiveReportingOrigins v) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerReportingOriginLimit, GetLimit(v.max)));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerReportingOriginLimit, GetLimit(v.max));
           },
           [](CreateReportResult::NoMatchingImpressions) {
-            return std::make_optional(
-                DebugDataTypeAndBody(DebugDataType::kTriggerNoMatchingSource));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerNoMatchingSource);
           },
           [](CreateReportResult::ExcessiveAttributions v) {
-            return std::make_optional(DebugDataTypeAndBody(
+            return std::make_optional<DebugDataTypeAndBody>(
                 DebugDataType::
                     kTriggerEventAttributionsPerSourceDestinationLimit,
-                GetLimit(v.max)));
+                GetLimit(v.max));
           },
           [](CreateReportResult::NoMatchingSourceFilterData) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerNoMatchingFilterData));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerNoMatchingFilterData);
           },
           [](CreateReportResult::Deduplicated) {
-            return std::make_optional(
-                DebugDataTypeAndBody(DebugDataType::kTriggerEventDeduplicated));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventDeduplicated);
           },
           [](CreateReportResult::NoMatchingConfigurations) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerEventNoMatchingConfigurations));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventNoMatchingConfigurations);
           },
           [](CreateReportResult::NeverAttributedSource) {
-            return std::make_optional(
-                DebugDataTypeAndBody(DebugDataType::kTriggerEventNoise));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventNoise);
           },
           [](CreateReportResult::FalselyAttributedSource) {
-            return std::make_optional(
-                DebugDataTypeAndBody(DebugDataType::kTriggerEventNoise));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventNoise);
           },
           [](const CreateReportResult::PriorityTooLow&) {
-            return std::make_optional(
-                DebugDataTypeAndBody(DebugDataType::kTriggerEventLowPriority));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventLowPriority);
           },
           [](const CreateReportResult::ExcessiveEventLevelReports&) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerEventExcessiveReports));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventExcessiveReports);
           },
           [](CreateReportResult::ReportWindowNotStarted) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerEventReportWindowNotStarted));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventReportWindowNotStarted);
           },
           [](CreateReportResult::ReportWindowPassed) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerEventReportWindowPassed));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventReportWindowPassed);
           },
           [](CreateReportResult::NoMatchingTriggerData) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerEventNoMatchingTriggerData));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerEventNoMatchingTriggerData);
           },
       },
       result);
@@ -243,57 +243,57 @@
             return std::optional<DebugDataTypeAndBody>();
           },
           [](CreateReportResult::InternalError) {
-            return std::make_optional(
-                DebugDataTypeAndBody(DebugDataType::kTriggerUnknownError));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerUnknownError);
           },
           [](CreateReportResult::NoCapacityForConversionDestination v) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerAggregateStorageLimit, GetLimit(v.max)));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerAggregateStorageLimit, GetLimit(v.max));
           },
           [](CreateReportResult::ExcessiveReportingOrigins v) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerReportingOriginLimit, GetLimit(v.max)));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerReportingOriginLimit, GetLimit(v.max));
           },
           [](CreateReportResult::NoMatchingImpressions) {
-            return std::make_optional(
-                DebugDataTypeAndBody(DebugDataType::kTriggerNoMatchingSource));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerNoMatchingSource);
           },
           [](CreateReportResult::ExcessiveAttributions v) {
-            return std::make_optional(DebugDataTypeAndBody(
+            return std::make_optional<DebugDataTypeAndBody>(
                 DebugDataType::
                     kTriggerAggregateAttributionsPerSourceDestinationLimit,
-                GetLimit(v.max)));
+                GetLimit(v.max));
           },
           [](CreateReportResult::NoMatchingSourceFilterData) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerNoMatchingFilterData));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerNoMatchingFilterData);
           },
           [](CreateReportResult::Deduplicated) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerAggregateDeduplicated));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerAggregateDeduplicated);
           },
           [](CreateReportResult::NoHistograms) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerAggregateNoContributions));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerAggregateNoContributions);
           },
           [](CreateReportResult::InsufficientBudget) {
-            return std::make_optional(DebugDataTypeAndBody(
+            return std::make_optional<DebugDataTypeAndBody>(
                 DebugDataType::kTriggerAggregateInsufficientBudget,
-                GetLimit(attribution_reporting::kMaxAggregatableValue)));
+                GetLimit(attribution_reporting::kMaxAggregatableValue));
           },
           [](const CreateReportResult::InsufficientNamedBudget& v) {
-            return std::make_optional(DebugDataTypeAndBody(
+            return std::make_optional<DebugDataTypeAndBody>(
                 DebugDataType::kTriggerAggregateInsufficientNamedBudget,
-                GetLimit(v.budget), base::Value::Dict().Set("name", v.name)));
+                GetLimit(v.budget), base::Value::Dict().Set("name", v.name));
           },
           [](CreateReportResult::ReportWindowPassed) {
-            return std::make_optional(DebugDataTypeAndBody(
-                DebugDataType::kTriggerAggregateReportWindowPassed));
+            return std::make_optional<DebugDataTypeAndBody>(
+                DebugDataType::kTriggerAggregateReportWindowPassed);
           },
           [](CreateReportResult::ExcessiveAggregatableReports v) {
-            return std::make_optional(DebugDataTypeAndBody(
+            return std::make_optional<DebugDataTypeAndBody>(
                 DebugDataType::kTriggerAggregateExcessiveReports,
-                GetLimit(v.max)));
+                GetLimit(v.max));
           },
       },
       result);
diff --git a/content/browser/attribution_reporting/attribution_host.cc b/content/browser/attribution_reporting/attribution_host.cc
index 3a03654..c1e7ece 100644
--- a/content/browser/attribution_reporting/attribution_host.cc
+++ b/content/browser/attribution_reporting/attribution_host.cc
@@ -337,7 +337,7 @@
   if (redirect_chain.size() < offset) {
     return;
   }
-  GURL reporting_url = redirect_chain[redirect_chain.size() - offset];
+  const GURL& reporting_url = redirect_chain[redirect_chain.size() - offset];
   // Pass the suitability as a proxy for the potentially trustworthy check, as
   // redirects should only happen for HTTP-based navigations.
   auto* tracker =
@@ -354,7 +354,7 @@
   bool had_header =
       attribution_manager->GetDataHostManager()
           ->NotifyNavigationRegistrationData(impression->attribution_src_token,
-                                             headers, std::move(reporting_url));
+                                             headers, reporting_url);
 
   if (had_header) {
     tracker->NotifySecureRegistrationAttempt();
diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
index 70435a40..0405fdaa0 100644
--- a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
+++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
@@ -17,6 +17,7 @@
 #include "base/check.h"
 #include "base/check_op.h"
 #include "base/containers/flat_map.h"
+#include "base/containers/to_vector.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
@@ -127,10 +128,7 @@
 
 std::vector<attribution_internals::mojom::WebUISourcePtr> ToWebUISources(
     const std::vector<StoredSource>& active_sources) {
-  std::vector<attribution_internals::mojom::WebUISourcePtr> web_ui_sources;
-  web_ui_sources.reserve(active_sources.size());
-
-  for (const StoredSource& source : active_sources) {
+  return base::ToVector(active_sources, [](const StoredSource& source) {
     Attributability attributability;
     switch (source.attribution_logic()) {
       case StoredSource::AttributionLogic::kTruthfully:
@@ -157,10 +155,8 @@
       }
     }
 
-    web_ui_sources.push_back(WebUISource(source, attributability));
-  }
-
-  return web_ui_sources;
+    return WebUISource(source, attributability);
+  });
 }
 
 attribution_internals::mojom::WebUIReportPtr WebUIReport(
@@ -191,9 +187,8 @@
                       /*value=*/0,
                       /*filtering_id=*/0));
             } else {
-              std::ranges::transform(
+              contributions = base::ToVector(
                   aggregatable_data.contributions(),
-                  std::back_inserter(contributions),
                   [](const auto& contribution) {
                     return ai_mojom::AggregatableHistogramContribution::New(
                         attribution_reporting::HexEncodeAggregationKey(
@@ -225,16 +220,10 @@
 
 std::vector<attribution_internals::mojom::WebUIReportPtr> ToWebUIReports(
     const std::vector<AttributionReport>& pending_reports) {
-  std::vector<attribution_internals::mojom::WebUIReportPtr> web_ui_reports;
-  web_ui_reports.reserve(pending_reports.size());
-
-  for (const AttributionReport& report : pending_reports) {
-    web_ui_reports.push_back(
-        WebUIReport(report, /*is_debug_report=*/false,
-                    ReportStatus::NewPending(Empty::New())));
-  }
-
-  return web_ui_reports;
+  return base::ToVector(pending_reports, [](const AttributionReport& report) {
+    return WebUIReport(report, /*is_debug_report=*/false,
+                       ReportStatus::NewPending(Empty::New()));
+  });
 }
 
 attribution_internals::mojom::NetworkStatusPtr NetworkStatus(int status) {
diff --git a/content/browser/attribution_reporting/attribution_os_level_manager_android.cc b/content/browser/attribution_reporting/attribution_os_level_manager_android.cc
index bc4621e3..b8a0817 100644
--- a/content/browser/attribution_reporting/attribution_os_level_manager_android.cc
+++ b/content/browser/attribution_reporting/attribution_os_level_manager_android.cc
@@ -17,6 +17,7 @@
 #include "base/barrier_closure.h"
 #include "base/check.h"
 #include "base/check_op.h"
+#include "base/containers/to_vector.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/location.h"
@@ -180,9 +181,8 @@
 
   Registrar registrar = registration.registrar;
   attribution_reporting::mojom::RegistrationType type = registration.GetType();
-  std::vector<ScopedJavaLocalRef<jobject>> registration_urls;
-  std::ranges::transform(
-      registration.registration_items, std::back_inserter(registration_urls),
+  std::vector<ScopedJavaLocalRef<jobject>> registration_urls = base::ToVector(
+      registration.registration_items,
       [env](const attribution_reporting::OsRegistrationItem& item) {
         return url::GURLAndroid::FromNativeGURL(env, item.url);
       });
@@ -280,9 +280,8 @@
 
   JNIEnv* env = AttachCurrentThread();
 
-  std::vector<ScopedJavaLocalRef<jobject>> j_origins;
-  std::ranges::transform(
-      origins, std::back_inserter(j_origins), [env](const url::Origin& origin) {
+  std::vector<ScopedJavaLocalRef<jobject>> j_origins =
+      base::ToVector(origins, [env](const url::Origin& origin) {
         return url::GURLAndroid::FromNativeGURL(env, origin.GetURL());
       });
 
@@ -292,8 +291,8 @@
   Java_AttributionOsLevelManager_deleteRegistrations(
       env, jobj_, request_id, delete_begin.InMillisecondsSinceUnixEpoch(),
       delete_end.InMillisecondsSinceUnixEpoch(), j_origins,
-      std::vector<std::string>(domains.begin(), domains.end()),
-      GetDeletionMode(delete_rate_limit_data), GetMatchBehavior(mode));
+      base::ToVector(domains), GetDeletionMode(delete_rate_limit_data),
+      GetMatchBehavior(mode));
 }
 
 void AttributionOsLevelManagerAndroid::OnRegistrationCompleted(JNIEnv* env,
diff --git a/content/browser/attribution_reporting/rate_limit_table.cc b/content/browser/attribution_reporting/rate_limit_table.cc
index b7c12c3..d5ad192 100644
--- a/content/browser/attribution_reporting/rate_limit_table.cc
+++ b/content/browser/attribution_reporting/rate_limit_table.cc
@@ -23,6 +23,7 @@
 #include "base/containers/flat_set.h"
 #include "base/containers/flat_tree.h"
 #include "base/containers/span.h"
+#include "base/containers/to_vector.h"
 #include "base/feature_list.h"
 #include "base/memory/raw_ref.h"
 #include "base/notreached.h"
@@ -592,19 +593,12 @@
   // Value is true if the reporting site matched, false otherwise.
   using DestinationSiteMap = base::flat_map<net::SchemefulSite, bool>;
 
-  DestinationSiteMap destination_sites = [&]() {
-    const base::flat_set<net::SchemefulSite>& destinations =
-        source.registration().destination_set.destinations();
-
-    DestinationSiteMap::container_type pairs;
-    pairs.reserve(destinations.size());
-
-    for (const net::SchemefulSite& site : destinations) {
-      pairs.emplace_back(site, true);
-    }
-
-    return DestinationSiteMap(base::sorted_unique, std::move(pairs));
-  }();
+  DestinationSiteMap destination_sites(
+      base::sorted_unique,
+      base::ToVector(source.registration().destination_set.destinations(),
+                     [](const net::SchemefulSite& site) {
+                       return std::make_pair(site, true);
+                     }));
 
   size_t num_with_same_reporting_site = destination_sites.size();
 
diff --git a/content/browser/attribution_reporting/sql_utils.cc b/content/browser/attribution_reporting/sql_utils.cc
index d65e6f7..20cc66c 100644
--- a/content/browser/attribution_reporting/sql_utils.cc
+++ b/content/browser/attribution_reporting/sql_utils.cc
@@ -20,6 +20,7 @@
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
 #include "base/containers/span.h"
+#include "base/containers/to_vector.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/time/time.h"
 #include "base/types/expected.h"
@@ -483,12 +484,9 @@
     return TriggerSpecs();
   }
 
-  std::vector<base::TimeDelta> end_times;
-  end_times.reserve(msg.event_level_report_window_end_times_size());
-
-  for (int64_t time : msg.event_level_report_window_end_times()) {
-    end_times.push_back(base::Microseconds(time));
-  }
+  std::vector<base::TimeDelta> end_times =
+      base::ToVector(msg.event_level_report_window_end_times(),
+                     [](int64_t time) { return base::Microseconds(time); });
 
   auto event_report_windows = EventReportWindows::Create(
       base::Microseconds(msg.event_level_report_window_start_time()),
diff --git a/content/browser/attribution_reporting/test/mock_attribution_data_host_manager.h b/content/browser/attribution_reporting/test/mock_attribution_data_host_manager.h
index cda6c86..d53803a 100644
--- a/content/browser/attribution_reporting/test/mock_attribution_data_host_manager.h
+++ b/content/browser/attribution_reporting/test/mock_attribution_data_host_manager.h
@@ -70,7 +70,7 @@
               NotifyNavigationRegistrationData,
               (const blink::AttributionSrcToken& attribution_src_token,
                const net::HttpResponseHeaders* headers,
-               GURL reporting_url),
+               const GURL& reporting_url),
               (override));
 
   MOCK_METHOD(void,
diff --git a/content/browser/media/capture/desktop_capture_device.cc b/content/browser/media/capture/desktop_capture_device.cc
index 93823d1..03c7429 100644
--- a/content/browser/media/capture/desktop_capture_device.cc
+++ b/content/browser/media/capture/desktop_capture_device.cc
@@ -851,6 +851,9 @@
 // static
 std::unique_ptr<media::VideoCaptureDevice> DesktopCaptureDevice::Create(
     const DesktopMediaID& source) {
+  CHECK(source.type == DesktopMediaID::TYPE_WINDOW ||
+        source.type == DesktopMediaID::TYPE_SCREEN);
+
   VLOG(1) << __func__ << "(source=" << source.ToString() << ")";
   auto options = desktop_capture::CreateDesktopCaptureOptions();
   std::unique_ptr<webrtc::DesktopCapturer> capturer;
@@ -890,13 +893,17 @@
           base::FeatureList::IsEnabled(features::kWebRtcAllowWgcWindowZeroHz));
     }
   }
+
+  options.set_wgc_require_border(
+      base::FeatureList::IsEnabled(features::kWebRtcWgcRequireBorder));
+
   VLOG(1) << "DesktopCaptureOptions: options={prefer_cursor_embedded: "
           << options.prefer_cursor_embedded() << ", allow_wgc_screen_capturer: "
           << options.allow_wgc_screen_capturer()
           << ", allow_wgc_window_capturer: "
           << options.allow_wgc_window_capturer()
           << ", allow_wgc_zero_hertz: " << options.allow_wgc_zero_hertz()
-          << "}";
+          << ", wgc_require_border: " << options.wgc_require_border() << "}";
 #endif
 
   // For browser tests, to create a fake desktop capturer.
diff --git a/content/browser/worker_host/shared_worker_host.cc b/content/browser/worker_host/shared_worker_host.cc
index 302a93a..f2aaa87 100644
--- a/content/browser/worker_host/shared_worker_host.cc
+++ b/content/browser/worker_host/shared_worker_host.cc
@@ -326,7 +326,7 @@
       instance_.url(), std::move(options),
       mojo::Clone(content_security_policies_),
       std::move(outside_fetch_client_settings_object),
-      instance_.same_site_cookies()));
+      instance_.same_site_cookies(), instance_.extended_lifetime()));
 
   auto renderer_preferences = blink::RendererPreferences();
   GetContentClient()->browser()->UpdateRendererPreferencesForWorker(
diff --git a/content/browser/worker_host/shared_worker_service_impl.cc b/content/browser/worker_host/shared_worker_service_impl.cc
index a7ee2af..7e70fa1 100644
--- a/content/browser/worker_host/shared_worker_service_impl.cc
+++ b/content/browser/worker_host/shared_worker_service_impl.cc
@@ -183,12 +183,24 @@
   SharedWorkerHost* host = FindMatchingSharedWorkerHost(
       info->url, info->options->name, storage_key, info->same_site_cookies);
   if (host) {
+    // TODO(crbug.com/413207418): revise ScriptLoadFailed() to use enum.
+
     // Non-secure contexts cannot connect to secure workers, and secure contexts
     // cannot connect to non-secure workers:
     if (host->instance().creation_context_type() != creation_context_type) {
       ScriptLoadFailed(std::move(client), /*error_message=*/"");
       return;
     }
+    // If extended_lifetime does not match, raise.
+    // See: https://github.com/whatwg/html/issues/10997#issuecomment-2791533299
+    if (host->instance().extended_lifetime() != info->extended_lifetime) {
+      ScriptLoadFailed(
+          std::move(client),
+          "Failed to connect an existing shared worker because the "
+          "extendedLifetime given on the SharedWorker constructor doesn't "
+          "match the existing shared worker's extendedLifetime.");
+      return;
+    }
     // Step 11.4: "If worker global scope is not null, then check if worker
     // global scope's type and credentials match the options values. If not,
     // queue a task to fire an event named error and abort these steps."
diff --git a/content/browser/worker_host/shared_worker_service_impl_unittest.cc b/content/browser/worker_host/shared_worker_service_impl_unittest.cc
index 731f25d..64ea2fe 100644
--- a/content/browser/worker_host/shared_worker_service_impl_unittest.cc
+++ b/content/browser/worker_host/shared_worker_service_impl_unittest.cc
@@ -65,7 +65,8 @@
       blink::mojom::FetchClientSettingsObject::New(
           network::mojom::ReferrerPolicy::kDefault, GURL(),
           blink::mojom::InsecureRequestsPolicy::kDoNotUpgrade),
-      blink::mojom::SharedWorkerSameSiteCookies::kAll));
+      blink::mojom::SharedWorkerSameSiteCookies::kAll,
+      /*extended_lifetime=*/false));
 
   blink::MessagePortDescriptorPair pipe;
   *local_port = MessagePortChannel(pipe.TakePort0());
diff --git a/content/web_test/browser/web_test_content_browser_client.cc b/content/web_test/browser/web_test_content_browser_client.cc
index 7c4f2f6..75bd6c08 100644
--- a/content/web_test/browser/web_test_content_browser_client.cc
+++ b/content/web_test/browser/web_test_content_browser_client.cc
@@ -628,12 +628,14 @@
     RenderFrameHost* render_frame_host,
     mojo::PendingReceiver<blink::test::mojom::CookieManagerAutomation>
         receiver) {
-  cookie_managers_.Add(std::make_unique<WebTestCookieManager>(
-                           GetWebTestBrowserContext()
-                               ->GetDefaultStoragePartition()
-                               ->GetCookieManagerForBrowserProcess(),
-                           render_frame_host->GetLastCommittedURL()),
-                       std::move(receiver));
+  cookie_managers_.Add(
+      std::make_unique<WebTestCookieManager>(
+          GetWebTestBrowserContext()
+              ->GetDefaultStoragePartition()
+              ->GetCookieManagerForBrowserProcess(),
+          render_frame_host->GetLastCommittedURL(),
+          render_frame_host->GetIsolationInfoForSubresources()),
+      std::move(receiver));
 }
 
 void WebTestContentBrowserClient::BindDevicePostureProviderAutomation(
diff --git a/content/web_test/browser/web_test_cookie_manager.cc b/content/web_test/browser/web_test_cookie_manager.cc
index edb4622..d6f0602 100644
--- a/content/web_test/browser/web_test_cookie_manager.cc
+++ b/content/web_test/browser/web_test_cookie_manager.cc
@@ -5,7 +5,9 @@
 #include "content/web_test/browser/web_test_cookie_manager.h"
 
 #include "content/public/browser/storage_partition.h"
+#include "net/base/schemeful_site.h"
 #include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_partition_key.h"
 #include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "url/gurl.h"
 
@@ -13,11 +15,22 @@
 
 WebTestCookieManager::WebTestCookieManager(
     network::mojom::CookieManager* const cookie_manager,
-    const GURL& url)
-    : cookie_manager_(cookie_manager), url_(url) {
+    const GURL& url,
+    const net::IsolationInfo& isolation_info)
+    : cookie_manager_(cookie_manager),
+      url_(url),
+      isolation_info_(isolation_info) {
   DCHECK(url_->is_valid());
 }
 
+std::optional<net::CookiePartitionKey>
+WebTestCookieManager::GetCookiePartitionKey() const {
+  return net::CookiePartitionKey::FromNetworkIsolationKey(
+      isolation_info_->network_isolation_key(),
+      isolation_info_->site_for_cookies(), net::SchemefulSite(*url_),
+      /*main_frame_navigation=*/false);
+}
+
 void WebTestCookieManager::DeleteAllCookies(
     blink::test::mojom::CookieManagerAutomation::DeleteAllCookiesCallback
         callback) {
@@ -41,7 +54,7 @@
         callback) {
   cookie_manager_->GetCookieList(
       *url_, net::CookieOptions::MakeAllInclusive(),
-      net::CookiePartitionKeyCollection(),
+      net::CookiePartitionKeyCollection(GetCookiePartitionKey()),
       base::BindOnce(
           [](blink::test::mojom::CookieManagerAutomation::GetAllCookiesCallback
                  callback,
@@ -58,7 +71,7 @@
         callback) {
   cookie_manager_->GetCookieList(
       *url_, net::CookieOptions::MakeAllInclusive(),
-      net::CookiePartitionKeyCollection(),
+      net::CookiePartitionKeyCollection(GetCookiePartitionKey()),
       base::BindOnce(
           [](const std::string& name,
              blink::test::mojom::CookieManagerAutomation::GetNamedCookieCallback
diff --git a/content/web_test/browser/web_test_cookie_manager.h b/content/web_test/browser/web_test_cookie_manager.h
index f43cb89..18d84583 100644
--- a/content/web_test/browser/web_test_cookie_manager.h
+++ b/content/web_test/browser/web_test_cookie_manager.h
@@ -7,6 +7,8 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
+#include "net/base/isolation_info.h"
+#include "net/cookies/cookie_partition_key.h"
 #include "services/network/public/mojom/cookie_manager.mojom-forward.h"
 #include "third_party/blink/public/mojom/cookie_manager/cookie_manager_automation.mojom.h"
 
@@ -19,7 +21,8 @@
  public:
   explicit WebTestCookieManager(
       network::mojom::CookieManager* const cookie_manager,
-      const GURL& url);
+      const GURL& url,
+      const net::IsolationInfo& isolation_info);
   ~WebTestCookieManager() override = default;
   WebTestCookieManager(const WebTestCookieManager&) = delete;
   WebTestCookieManager& operator=(const WebTestCookieManager&) = delete;
@@ -37,8 +40,11 @@
       override;
 
  private:
+  std::optional<net::CookiePartitionKey> GetCookiePartitionKey() const;
+
   const raw_ptr<network::mojom::CookieManager> cookie_manager_;
   const raw_ref<const GURL> url_;
+  const raw_ref<const net::IsolationInfo> isolation_info_;
 };
 
 }  // namespace content
diff --git a/gin/context_holder.cc b/gin/context_holder.cc
index ab4f049a..f891e6e1 100644
--- a/gin/context_holder.cc
+++ b/gin/context_holder.cc
@@ -11,14 +11,9 @@
 
 namespace gin {
 
-ContextHolder::ContextHolder(v8::Isolate* isolate)
-    : isolate_(isolate) {
-}
+ContextHolder::ContextHolder(v8::Isolate* isolate) : isolate_(isolate) {}
 
-ContextHolder::~ContextHolder() {
-  // PerContextData needs to be destroyed before the context.
-  data_.reset();
-}
+ContextHolder::~ContextHolder() = default;
 
 void ContextHolder::SetContext(v8::Local<v8::Context> context) {
   DCHECK(context_.IsEmpty());
diff --git a/gin/public/context_holder.h b/gin/public/context_holder.h
index f37dd61..ec9a105 100644
--- a/gin/public/context_holder.h
+++ b/gin/public/context_holder.h
@@ -15,9 +15,9 @@
 
 namespace gin {
 
-// Gin embedder that store embedder data in v8::Contexts must do so in a
-// single field with the index kPerContextDataStartIndex + GinEmbedder-enum.
-// The field at kDebugIdIndex is treated specially by V8 and is reserved for
+// Gin embedder that store embedder data in `v8::Context`s must do so in a
+// single field with the index `kPerContextDataStartIndex + GinEmbedder-enum`.
+// The field at `kDebugIdIndex` is treated specially by V8 and is reserved for
 // a V8 debugger implementation (not used by gin).
 enum ContextEmbedderDataFields {
   kDebugIdIndex = v8::Context::kDebugIdIndex,
@@ -26,16 +26,21 @@
 
 class PerContextData;
 
-// ContextHolder is a generic class for holding a v8::Context.
+// ContextHolder is a generic class for holding a `v8::Context`.
 class GIN_EXPORT ContextHolder {
  public:
   explicit ContextHolder(v8::Isolate* isolate);
+  // Note that also the destructor needs a `v8::HandleScope` to be in scope
+  // because it calls the `context()` method via the contained `PerContextData`.
+  ~ContextHolder();
+
   ContextHolder(const ContextHolder&) = delete;
   ContextHolder& operator=(const ContextHolder&) = delete;
-  ~ContextHolder();
 
   v8::Isolate* isolate() const { return isolate_; }
 
+  // Return the held context in a new `v8::Local`; this requires a
+  // `v8::HandleScope` to be set up by the caller.
   v8::Local<v8::Context> context() const {
     return v8::Local<v8::Context>::New(isolate_, context_);
   }
@@ -43,8 +48,9 @@
   void SetContext(v8::Local<v8::Context> context);
 
  private:
-  raw_ptr<v8::Isolate> isolate_;
+  const raw_ptr<v8::Isolate> isolate_;
   v8::UniquePersistent<v8::Context> context_;
+  // Data is declared after `context_` so it gets destructed first.
   std::unique_ptr<PerContextData> data_;
 };
 
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index d17b604..5c13343 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -17233,7 +17233,7 @@
         '  "led_builder_is_bootstrapped": true,'
         '  "recipe": "chromium"'
         '}'
-      execution_timeout_secs: 10800
+      execution_timeout_secs: 21600
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
diff --git a/infra/config/subprojects/chromium/ci/chromium.gpu.star b/infra/config/subprojects/chromium/ci/chromium.gpu.star
index d18501f5..01f5418 100644
--- a/infra/config/subprojects/chromium/ci/chromium.gpu.star
+++ b/infra/config/subprojects/chromium/ci/chromium.gpu.star
@@ -419,6 +419,9 @@
     console_view_entry = consoles.console_view_entry(
         category = "Windows",
     ),
+    # TODO(crbug.com/413285147): Restore this to the default once sync/compile
+    # times are reduced.
+    execution_timeout = 6 * time.hour,
 )
 
 ci.thin_tester(
diff --git a/internal b/internal
index ef1a2d4..c3f4877 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit ef1a2d488c86cc10f9d96b94f797f42fb4273095
+Subproject commit c3f48770647d6163d3d4082206808ec6fea359ae
diff --git a/ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.mm b/ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.mm
index 8ad6a3fcb..d0df0a1 100644
--- a/ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.mm
+++ b/ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.mm
@@ -26,7 +26,7 @@
 // static
 affiliations::AffiliationService*
 IOSChromeAffiliationServiceFactory::GetForProfile(ProfileIOS* profile) {
-  CHECK(profile, base::NotFatalUntil::M123);
+  CHECK(profile);
 
   return static_cast<affiliations::AffiliationService*>(
       GetInstance()->GetServiceForBrowserState(profile, true));
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/BUILD.gn b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/BUILD.gn
index d767629..8fe2473 100644
--- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/BUILD.gn
+++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/BUILD.gn
@@ -29,7 +29,6 @@
     "//ios/chrome/browser/authentication/ui_bundled",
     "//ios/chrome/browser/authentication/ui_bundled:authentication_constants",
     "//ios/chrome/browser/authentication/ui_bundled/enterprise/managed_profile_creation",
-    "//ios/chrome/browser/authentication/ui_bundled/history_sync:capabilities_fetcher",
     "//ios/chrome/browser/authentication/ui_bundled/signin:constants",
     "//ios/chrome/browser/authentication/ui_bundled/signin:signin_headers",
     "//ios/chrome/browser/flags",
@@ -69,7 +68,6 @@
     "//ios/chrome/app/application_delegate:app_state",
     "//ios/chrome/app/profile",
     "//ios/chrome/browser/authentication/ui_bundled:continuation",
-    "//ios/chrome/browser/authentication/ui_bundled/history_sync:capabilities_fetcher",
     "//ios/chrome/browser/policy/model",
     "//ios/chrome/browser/shared/coordinator/scene:scene_state_header",
     "//ios/chrome/browser/shared/model/application_context",
@@ -106,6 +104,7 @@
     "//ios/chrome/browser/authentication/ui_bundled",
     "//ios/chrome/browser/authentication/ui_bundled:authentication_constants",
     "//ios/chrome/browser/authentication/ui_bundled/enterprise/managed_profile_creation",
+    "//ios/chrome/browser/authentication/ui_bundled/history_sync:capabilities_fetcher",
     "//ios/chrome/browser/authentication/ui_bundled/signin:signin_headers",
     "//ios/chrome/browser/policy/model",
     "//ios/chrome/browser/policy/ui_bundled:management_util",
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm
index 54e5f6d..cb60582 100644
--- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm
@@ -30,7 +30,6 @@
 #import "ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.h"
 #import "ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_request_helper.h"
 #import "ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.h"
-#import "ios/chrome/browser/authentication/ui_bundled/history_sync/history_sync_capabilities_fetcher.h"
 #import "ios/chrome/browser/flags/ios_chrome_flag_descriptions.h"
 #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.h"
 #import "ios/chrome/browser/policy/model/browser_policy_connector_ios.h"
@@ -941,6 +940,10 @@
   [self continueFlow];
 }
 
+- (void)didFetchAccountCapabilities {
+  NOTREACHED();
+}
+
 #pragma mark - Private methods
 
 // Returns the request helper exactly once. CHECK fail if its accessed twice.
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_in_profile.mm b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_in_profile.mm
index f089df9..6e72106 100644
--- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_in_profile.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_in_profile.mm
@@ -16,7 +16,6 @@
 #import "ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.h"
 #import "ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer_delegate.h"
 #import "ios/chrome/browser/authentication/ui_bundled/continuation.h"
-#import "ios/chrome/browser/authentication/ui_bundled/history_sync/history_sync_capabilities_fetcher.h"
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
@@ -77,8 +76,6 @@
   // to compare with a similiar list from device mangement to understand whether
   // user and device are managed by the same domain.
   NSArray<NSString*>* _userAffiliationIDs;
-  // Capabilities fetcher for the subsequent History Sync Opt-In screen.
-  HistorySyncCapabilitiesFetcher* _capabilitiesFetcher;
 
   // The lifetime of this ScopedClosureRunner denotes a batch of primary account
   // changes. UI listens to batched changes to avoid visual artifacts during an
@@ -357,19 +354,7 @@
     [self continueFlow];
     return;
   }
-  ProfileIOS* profile = [self originalProfile];
-  // Create the capability fetcher and start fetching capabilities.
-  __weak __typeof(self) weakSelf = self;
-  _capabilitiesFetcher = [[HistorySyncCapabilitiesFetcher alloc]
-      initWithIdentityManager:IdentityManagerFactory::GetForProfile(profile)];
-
-  [_capabilitiesFetcher
-      startFetchingRestrictionCapabilityWithCallback:base::BindOnce(^(
-                                                         signin::Tribool
-                                                             capability) {
-        // The capability value is ignored.
-        [weakSelf continueFlow];
-      })];
+  [_performer fetchAccountCapabilities:[self originalProfile]];
 }
 
 - (void)successCompleteFlowStep {
@@ -527,4 +512,8 @@
   NOTREACHED();
 }
 
+- (void)didFetchAccountCapabilities {
+  [self continueFlow];
+}
+
 @end
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.h b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.h
index 1d17962..a10d12e 100644
--- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.h
+++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.h
@@ -147,6 +147,8 @@
      userAffiliationIDs:(NSArray<NSString*>*)userAffiliationIDs
                identity:(id<SystemIdentity>)identity;
 
+- (void)fetchAccountCapabilities:(ProfileIOS*)profile;
+
 @property(nonatomic, weak, readonly) id<AuthenticationFlowPerformerDelegate>
     delegate;
 
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.mm b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.mm
index 7182eef1..a2784b4 100644
--- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.mm
+++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer.mm
@@ -35,6 +35,7 @@
 #import "ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_request_helper.h"
 #import "ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.h"
 #import "ios/chrome/browser/authentication/ui_bundled/enterprise/managed_profile_creation/managed_profile_creation_coordinator.h"
+#import "ios/chrome/browser/authentication/ui_bundled/history_sync/history_sync_capabilities_fetcher.h"
 #import "ios/chrome/browser/authentication/ui_bundled/signin/signin_utils.h"
 #import "ios/chrome/browser/policy/model/browser_policy_connector_ios.h"
 #import "ios/chrome/browser/policy/model/cloud/user_policy_signin_service.h"
@@ -144,6 +145,8 @@
   // calls it.
   std::unique_ptr<policy::UserCloudSigninRestrictionPolicyFetcher>
       _accountLevelSigninRestrictionPolicyFetcher;
+  // Capabilities fetcher for the subsequent History Sync Opt-In screen.
+  HistorySyncCapabilitiesFetcher* _capabilitiesFetcher;
   std::unique_ptr<base::OneShotTimer> _watchdogTimer;
   id<ChangeProfileCommands> _changeProfileHandler;
   ActionSheetCoordinator* _leavingPrimaryAccountConfirmationDialogCoordinator;
@@ -550,6 +553,21 @@
   [_delegate didFetchUserPolicyWithSuccess:success];
 }
 
+- (void)fetchAccountCapabilities:(ProfileIOS*)profile {
+  // Create the capability fetcher and start fetching capabilities.
+  _capabilitiesFetcher = [[HistorySyncCapabilitiesFetcher alloc]
+      initWithIdentityManager:IdentityManagerFactory::GetForProfile(profile)];
+
+  __weak __typeof(self) weakSelf = self;
+  [_capabilitiesFetcher
+      startFetchingRestrictionCapabilityWithCallback:base::BindOnce(^(
+                                                         signin::Tribool
+                                                             capability) {
+        // The capability value is ignored.
+        [weakSelf didFetchAccountCapabilities];
+      })];
+}
+
 #pragma mark - Private
 
 // The change profile continuation for the authentication flow.
@@ -580,6 +598,10 @@
                  profile_separation_data_migration_settings];
 }
 
+- (void)didFetchAccountCapabilities {
+  [_delegate didFetchAccountCapabilities];
+}
+
 - (void)updateUserPolicyNotificationStatusIfNeeded:(PrefService*)prefService {
   prefService->SetBoolean(policy::policy_prefs::kUserPolicyNotificationWasShown,
                           true);
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer_delegate.h b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer_delegate.h
index 11b6b4f..8416b4c5e 100644
--- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer_delegate.h
+++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow_performer_delegate.h
@@ -78,6 +78,9 @@
 // Indicates that the personal profile was converted to a managed one.
 - (void)didMakePersonalProfileManaged;
 
+// Indicates that account capabilities have been fetched.
+- (void)didFetchAccountCapabilities;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_AUTHENTICATION_UI_BUNDLED_AUTHENTICATION_FLOW_AUTHENTICATION_FLOW_PERFORMER_DELEGATE_H_
diff --git a/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_mediator_unittest.mm b/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_mediator_unittest.mm
index d1802db..4aeca6f 100644
--- a/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_mediator_unittest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_mediator_unittest.mm
@@ -6,6 +6,7 @@
 
 #import "base/memory/weak_ptr.h"
 #import "base/strings/sys_string_conversions.h"
+#import "components/autofill/core/browser/data_model/payments/credit_card.h"
 #import "components/autofill/core/browser/payments/otp_unmask_delegate.h"
 #import "components/autofill/core/browser/ui/payments/card_unmask_otp_input_dialog_controller_impl.h"
 #import "ios/chrome/browser/autofill/ui_bundled/authentication/otp_input_dialog_consumer.h"
@@ -44,7 +45,9 @@
   base::WeakPtrFactory<MockOtpUnmaskDelegate> weak_ptr_factory_{this};
 };
 
-class OtpInputDialogMediatorTest : public PlatformTest {
+class OtpInputDialogMediatorTest
+    : public PlatformTest,
+      public ::testing::WithParamInterface<autofill::CreditCard::RecordType> {
  protected:
   OtpInputDialogMediatorTest() {
     consumer_ = OCMProtocolMock(@protocol(OtpInputDialogConsumer));
@@ -54,9 +57,10 @@
         autofill::CardUnmaskChallengeOptionType::kSmsOtp,
         /*challenge_info=*/u"xxx-xxx-3547",
         /*challenge_input_length=*/6U);
+    autofill::CreditCard::RecordType card_type = GetParam();
     model_controller_ =
         std::make_unique<autofill::CardUnmaskOtpInputDialogControllerImpl>(
-            option, unmask_delegate_.GetWeakPtr());
+            card_type, option, unmask_delegate_.GetWeakPtr());
     mediator_ = std::make_unique<OtpInputDialogMediator>(
         model_controller_->GetImplWeakPtr(), delegate_);
   }
@@ -70,7 +74,7 @@
 };
 
 // Tests consumer receives the correct contents.
-TEST_F(OtpInputDialogMediatorTest, SetConsumer) {
+TEST_P(OtpInputDialogMediatorTest, SetConsumer) {
   OCMExpect([consumer_
       setContent:[OCMArg checkWithBlock:^BOOL(OtpInputDialogContent* content) {
         EXPECT_NSEQ(
@@ -90,7 +94,7 @@
   EXPECT_OCMOCK_VERIFY((id)consumer_);
 }
 
-TEST_F(OtpInputDialogMediatorTest, DidTapConfirmButton) {
+TEST_P(OtpInputDialogMediatorTest, DidTapConfirmButton) {
   NSString* otp = @"123456";
   OCMExpect([consumer_ showPendingState]);
   EXPECT_CALL(unmask_delegate_,
@@ -99,7 +103,7 @@
   [mediator_->AsMutator() didTapConfirmButton:otp];
 }
 
-TEST_F(OtpInputDialogMediatorTest, DidTapCancelButton) {
+TEST_P(OtpInputDialogMediatorTest, DidTapCancelButton) {
   OCMExpect([delegate_ dismissDialog]);
   EXPECT_CALL(unmask_delegate_,
               OnUnmaskPromptClosed(/*user_closed_dialog=*/true));
@@ -107,7 +111,7 @@
   [mediator_->AsMutator() didTapCancelButton];
 }
 
-TEST_F(OtpInputDialogMediatorTest, OnOtpInputChanges) {
+TEST_P(OtpInputDialogMediatorTest, OnOtpInputChanges) {
   OCMExpect([consumer_ setConfirmButtonEnabled:NO]);
 
   [mediator_->AsMutator() onOtpInputChanges:@"12345"];
@@ -117,7 +121,7 @@
   [mediator_->AsMutator() onOtpInputChanges:@"123456"];
 }
 
-TEST_F(OtpInputDialogMediatorTest, Dismiss) {
+TEST_P(OtpInputDialogMediatorTest, Dismiss) {
   OCMExpect([delegate_ dismissDialog]);
   EXPECT_CALL(unmask_delegate_,
               OnUnmaskPromptClosed(/*user_closed_dialog=*/true));
@@ -126,8 +130,14 @@
                      /*user_closed_dialog=*/true);
 }
 
-TEST_F(OtpInputDialogMediatorTest, NewCodeRequested) {
+TEST_P(OtpInputDialogMediatorTest, NewCodeRequested) {
   EXPECT_CALL(unmask_delegate_, OnNewOtpRequested());
 
   [mediator_->AsMutator() didTapNewCodeLink];
 }
+
+INSTANTIATE_TEST_SUITE_P(
+    /* No InstantiationName */,
+    OtpInputDialogMediatorTest,
+    testing::Values(autofill::CreditCard::RecordType::kLocalCard,
+                    autofill::CreditCard::RecordType::kMaskedServerCard));
diff --git a/ios/chrome/browser/autofill/ui_bundled/ios_chrome_payments_autofill_client.h b/ios/chrome/browser/autofill/ui_bundled/ios_chrome_payments_autofill_client.h
index be5d2af1..04c8ab8 100644
--- a/ios/chrome/browser/autofill/ui_bundled/ios_chrome_payments_autofill_client.h
+++ b/ios/chrome/browser/autofill/ui_bundled/ios_chrome_payments_autofill_client.h
@@ -86,6 +86,7 @@
       base::OnceClosure decline_virtual_card_callback) override;
   void VirtualCardEnrollCompleted(PaymentsRpcResult result) override;
   void ShowCardUnmaskOtpInputDialog(
+      CreditCard::RecordType card_type,
       const CardUnmaskChallengeOption& challenge_option,
       base::WeakPtr<OtpUnmaskDelegate> delegate) override;
   void OnUnmaskOtpVerificationResult(OtpUnmaskResult unmask_result) override;
diff --git a/ios/chrome/browser/autofill/ui_bundled/ios_chrome_payments_autofill_client.mm b/ios/chrome/browser/autofill/ui_bundled/ios_chrome_payments_autofill_client.mm
index 056ea2e..4c987294 100644
--- a/ios/chrome/browser/autofill/ui_bundled/ios_chrome_payments_autofill_client.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/ios_chrome_payments_autofill_client.mm
@@ -231,11 +231,12 @@
 }
 
 void IOSChromePaymentsAutofillClient::ShowCardUnmaskOtpInputDialog(
+    CreditCard::RecordType card_type,
     const CardUnmaskChallengeOption& challenge_option,
     base::WeakPtr<OtpUnmaskDelegate> delegate) {
   otp_input_dialog_controller_ =
-      std::make_unique<CardUnmaskOtpInputDialogControllerImpl>(challenge_option,
-                                                               delegate);
+      std::make_unique<CardUnmaskOtpInputDialogControllerImpl>(
+          card_type, challenge_option, delegate);
   otp_input_dialog_controller_weak_ =
       otp_input_dialog_controller_->GetImplWeakPtr();
   [client_->commands_handler() continueCardUnmaskWithOtpAuth];
diff --git a/ios/chrome/browser/browser_view/OWNERS b/ios/chrome/browser/browser_view/OWNERS
index 26a157f..a5b0d639 100644
--- a/ios/chrome/browser/browser_view/OWNERS
+++ b/ios/chrome/browser/browser_view/OWNERS
@@ -2,10 +2,8 @@
 rohitrao@chromium.org
 fedegermi@google.com
 
-per-file browser_coordinator.*=edchin@chromium.org
 per-file key_commands_provider*=lpromero@google.com
 
 # BVC OWNERS for small changes. Please refer larger changes to one
 # of the above OWNERs.
-per-file browser_view_controller*=edchin@chromium.org
 per-file browser_view_controller*=gambard@chromium.org
diff --git a/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn b/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn
index b6ae6320..9c72099 100644
--- a/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn
+++ b/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn
@@ -195,6 +195,7 @@
     "//ios/chrome/browser/promos_manager/model:features",
     "//ios/chrome/browser/promos_manager/ui_bundled",
     "//ios/chrome/browser/qr_scanner/ui_bundled:coordinator",
+    "//ios/chrome/browser/reader_mode/coordinator",
     "//ios/chrome/browser/reader_mode/model",
     "//ios/chrome/browser/reader_mode/model:features",
     "//ios/chrome/browser/reading_list/model",
diff --git a/ios/chrome/browser/browser_view/ui_bundled/DEPS b/ios/chrome/browser/browser_view/ui_bundled/DEPS
index 2dd599e..bba8ec5 100644
--- a/ios/chrome/browser/browser_view/ui_bundled/DEPS
+++ b/ios/chrome/browser/browser_view/ui_bundled/DEPS
@@ -36,6 +36,7 @@
   "+ios/chrome/browser/follow/model",
   "+ios/chrome/browser/follow/ui_bundled",
   "+ios/chrome/browser/fullscreen/ui_bundled",
+  "+ios/chrome/browser/reader_mode/coordinator",
   "+ios/chrome/browser/google_one/coordinator",
   "+ios/chrome/browser/incognito_reauth/ui_bundled",
   "+ios/chrome/browser/infobars/model",
diff --git a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm
index 291e4a6..172af61 100644
--- a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm
+++ b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm
@@ -162,6 +162,7 @@
 #import "ios/chrome/browser/promos_manager/model/features.h"
 #import "ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator.h"
 #import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_legacy_coordinator.h"
+#import "ios/chrome/browser/reader_mode/coordinator/reader_mode_coordinator.h"
 #import "ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.h"
 #import "ios/chrome/browser/reading_list/model/reading_list_browser_agent.h"
 #import "ios/chrome/browser/reading_list/ui_bundled/reading_list_coordinator.h"
@@ -641,6 +642,7 @@
   ContextualSheetCoordinator* _contextualSheetCoordinator;
   RootDriveFilePickerCoordinator* _driveFilePickerCoordinator;
   GoogleOneCoordinator* _googleOneCoordinator;
+  ReaderModeCoordinator* _readerModeCoordinator;
   // Coordinator to display the "Set a reminder" screen for the user's current
   // tab.
   ReminderNotificationsCoordinator* _reminderNotificationsCoordinator;
@@ -2595,16 +2597,69 @@
   if (!activeWebState) {
     return;
   }
-  ReaderModeTabHelper* readerModeTabHelper =
+  ReaderModeTabHelper* tabHelper =
       ReaderModeTabHelper::FromWebState(activeWebState);
-  if (!readerModeTabHelper) {
+  if (!tabHelper) {
     return;
   }
-  readerModeTabHelper->TriggerReaderModeHeuristic();
+  if (!tabHelper->IsActive()) {
+    // If Reader mode is not active yet in this tab, activate it first. When the
+    // distilled page is ready, -showReaderMode will be called again and the
+    // Reader mode UI can be presented.
+    tabHelper->SetActive(true);
+    return;
+  }
+  if (_readerModeCoordinator) {
+    // If the Reader mode UI is already presented then there is nothing to do.
+    return;
+  }
+  _readerModeCoordinator = [[ReaderModeCoordinator alloc]
+      initWithBaseViewController:self.browserContainerCoordinator.viewController
+                         browser:self.browser];
+  [_readerModeCoordinator start];
 }
 
 - (void)hideReaderMode {
-  // TODO(crbug.com/409940117): Hide reader mode UI when requested.
+  web::WebState* activeWebState = self.activeWebState;
+  if (!activeWebState) {
+    return;
+  }
+  ReaderModeTabHelper* tabHelper =
+      ReaderModeTabHelper::FromWebState(activeWebState);
+  if (!tabHelper) {
+    return;
+  }
+  if (tabHelper->IsActive()) {
+    // If Reader mode is active in this tab, deactivate it first. When it has
+    // been deactivated, -hideReaderMode will be called again and the Reader
+    // mode UI can be dismissed.
+    tabHelper->SetActive(false);
+    return;
+  }
+  if (!_readerModeCoordinator) {
+    // If the Reader mode UI is already dismissed then there is nothing to do.
+    return;
+  }
+  [_readerModeCoordinator stop];
+  _readerModeCoordinator = nil;
+}
+
+- (void)toggleReaderMode {
+  web::WebState* activeWebState = self.activeWebState;
+  if (!activeWebState) {
+    return;
+  }
+  ReaderModeTabHelper* tabHelper =
+      ReaderModeTabHelper::FromWebState(activeWebState);
+  if (!tabHelper) {
+    return;
+  }
+  // If Reader mode is active in the current tab, hide it. Otherwise, show it.
+  if (tabHelper->IsActive()) {
+    [self hideReaderMode];
+  } else {
+    [self showReaderMode];
+  }
 }
 
 #pragma mark - FindInPageCommands
diff --git a/ios/chrome/browser/browser_view/ui_bundled/tab_lifecycle_mediator.mm b/ios/chrome/browser/browser_view/ui_bundled/tab_lifecycle_mediator.mm
index fd37b601..005f79d 100644
--- a/ios/chrome/browser/browser_view/ui_bundled/tab_lifecycle_mediator.mm
+++ b/ios/chrome/browser/browser_view/ui_bundled/tab_lifecycle_mediator.mm
@@ -33,6 +33,7 @@
 #import "ios/chrome/browser/shared/public/commands/lens_commands.h"
 #import "ios/chrome/browser/shared/public/commands/mini_map_commands.h"
 #import "ios/chrome/browser/shared/public/commands/parent_access_commands.h"
+#import "ios/chrome/browser/shared/public/commands/reader_mode_commands.h"
 #import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
 #import "ios/chrome/browser/shared/public/commands/unit_conversion_commands.h"
 #import "ios/chrome/browser/shared/public/commands/web_content_commands.h"
@@ -158,6 +159,13 @@
         HandlerForProtocol(_commandDispatcher, SnackbarCommands));
   }
 
+  if (IsReaderModeAvailable()) {
+    ReaderModeTabHelper* readerModeTabHelper =
+        ReaderModeTabHelper::FromWebState(webState);
+    readerModeTabHelper->SetReaderModeHandler(
+        HandlerForProtocol(_commandDispatcher, ReaderModeCommands));
+  }
+
   DCHECK(_printCoordinator);
   PrintTabHelper::GetOrCreateForWebState(webState)->set_printer(
       _printCoordinator);
@@ -259,6 +267,12 @@
     readerModeTabHelper->SetSnackbarHandler(nil);
   }
 
+  if (IsReaderModeAvailable()) {
+    ReaderModeTabHelper* readerModeTabHelper =
+        ReaderModeTabHelper::FromWebState(webState);
+    readerModeTabHelper->SetReaderModeHandler(nil);
+  }
+
   PrintTabHelper::GetOrCreateForWebState(webState)->set_printer(nil);
 
   RepostFormTabHelper::FromWebState(webState)->SetDelegate(nil);
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_saver_unittest.mm b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_saver_unittest.mm
index bf1429f..e67e2b3 100644
--- a/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_saver_unittest.mm
+++ b/ios/chrome/browser/content_suggestions/ui_bundled/cells/content_suggestions_tile_saver_unittest.mm
@@ -221,7 +221,6 @@
   VerifyWithImage(image_saved_tile, image_title, image_url);
 }
 
-// TODO(crbug.com/40071361): reenable this test.
 TEST_F(ContentSuggestionsTileSaverControllerTest, UpdateSingleFaviconFallback) {
   // Set up test with 3 saved sites, 2 of which have a favicon.
   ntp_tiles::NTPTile image_tile1 = ntp_tiles::NTPTile();
diff --git a/ios/chrome/browser/discover_feed/model/OWNERS b/ios/chrome/browser/discover_feed/model/OWNERS
index 204619bf..f97033d4 100644
--- a/ios/chrome/browser/discover_feed/model/OWNERS
+++ b/ios/chrome/browser/discover_feed/model/OWNERS
@@ -1,4 +1,2 @@
 sczs@chromium.org
 adamta@google.com
-
-per-file discover_feed_app_agent*=edchin@google.com
diff --git a/ios/chrome/browser/drag_and_drop/model/OWNERS b/ios/chrome/browser/drag_and_drop/model/OWNERS
index af2524f..96e7948a 100644
--- a/ios/chrome/browser/drag_and_drop/model/OWNERS
+++ b/ios/chrome/browser/drag_and_drop/model/OWNERS
@@ -1,2 +1 @@
-edchin@chromium.org
 stkhapugin@chromium.org
diff --git a/ios/chrome/browser/favicon/model/OWNERS b/ios/chrome/browser/favicon/model/OWNERS
deleted file mode 100644
index 3baf3c0..0000000
--- a/ios/chrome/browser/favicon/model/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-edchin@chromium.org
diff --git a/ios/chrome/browser/follow/ui_bundled/OWNERS b/ios/chrome/browser/follow/ui_bundled/OWNERS
index 34a57184..784ebbe 100644
--- a/ios/chrome/browser/follow/ui_bundled/OWNERS
+++ b/ios/chrome/browser/follow/ui_bundled/OWNERS
@@ -1,3 +1,2 @@
 adamta@google.com
-edchin@google.com
 tinazwang@google.com
diff --git a/ios/chrome/browser/main/ui_bundled/OWNERS b/ios/chrome/browser/main/ui_bundled/OWNERS
index b98e1d4..f49ed38 100644
--- a/ios/chrome/browser/main/ui_bundled/OWNERS
+++ b/ios/chrome/browser/main/ui_bundled/OWNERS
@@ -1,2 +1 @@
-edchin@chromium.org
 marq@chromium.org
diff --git a/ios/chrome/browser/metrics/model/pageload_foreground_duration_tab_helper.mm b/ios/chrome/browser/metrics/model/pageload_foreground_duration_tab_helper.mm
index 1c0b4d9..74a8db1 100644
--- a/ios/chrome/browser/metrics/model/pageload_foreground_duration_tab_helper.mm
+++ b/ios/chrome/browser/metrics/model/pageload_foreground_duration_tab_helper.mm
@@ -119,8 +119,8 @@
 }
 
 void PageloadForegroundDurationTabHelper::CreateNotificationObservers() {
-  CHECK(!background_notification_observer_, base::NotFatalUntil::M125);
-  CHECK(!foreground_notification_observer_, base::NotFatalUntil::M125);
+  CHECK(!background_notification_observer_);
+  CHECK(!foreground_notification_observer_);
 
   base::RepeatingCallback<void(NSNotification*)> backgrounding_closure =
       base::IgnoreArgs<NSNotification*>(base::BindRepeating(
diff --git a/ios/chrome/browser/ntp/model/OWNERS b/ios/chrome/browser/ntp/model/OWNERS
index 8f0235c2..50dbcec 100644
--- a/ios/chrome/browser/ntp/model/OWNERS
+++ b/ios/chrome/browser/ntp/model/OWNERS
@@ -2,5 +2,3 @@
 justincohen@chromium.org
 adamta@google.com
 scottyoder@google.com
-
-per-file features.*=edchin@google.com
diff --git a/ios/chrome/browser/omnibox/coordinator/omnibox_coordinator.mm b/ios/chrome/browser/omnibox/coordinator/omnibox_coordinator.mm
index 597afb8..f561a78 100644
--- a/ios/chrome/browser/omnibox/coordinator/omnibox_coordinator.mm
+++ b/ios/chrome/browser/omnibox/coordinator/omnibox_coordinator.mm
@@ -195,8 +195,7 @@
                 controller:_editView->controller()];
 
   _omniboxAutocompleteController = [[OmniboxAutocompleteController alloc]
-      initWithOmniboxController:_editView->controller()
-                 omniboxViewIOS:_editView.get()];
+      initWithOmniboxController:_editView->controller()];
 
   _omniboxTextController = [[OmniboxTextController alloc]
       initWithOmniboxController:_editView->controller()
diff --git a/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.h b/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.h
index 888581d1..90f5726 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.h
+++ b/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.h
@@ -18,7 +18,6 @@
 @protocol OmniboxAutocompleteControllerDebuggerDelegate;
 class OmniboxController;
 @class OmniboxTextController;
-class OmniboxViewIOS;
 
 /// Controller for the omnibox autocomplete system. Handles interactions with
 /// the autocomplete system and dispatches results.
@@ -44,7 +43,6 @@
 
 /// Initializes with an OmniboxController.
 - (instancetype)initWithOmniboxController:(OmniboxController*)omniboxController
-                           omniboxViewIOS:(OmniboxViewIOS*)omniboxViewIOS
     NS_DESIGNATED_INITIALIZER;
 - (instancetype)init NS_UNAVAILABLE;
 
diff --git a/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.mm b/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.mm
index a2ebf08..3376aa8 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller.mm
@@ -23,7 +23,6 @@
 #import "ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller_debugger_delegate.h"
 #import "ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller_delegate.h"
 #import "ios/chrome/browser/omnibox/model/omnibox_text_controller.h"
-#import "ios/chrome/browser/omnibox/ui_bundled/omnibox_view_ios.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_backed_boolean.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
@@ -48,8 +47,6 @@
 @implementation OmniboxAutocompleteController {
   /// Controller of the omnibox.
   raw_ptr<OmniboxController> _omniboxController;
-  /// Controller of the omnibox view.
-  raw_ptr<OmniboxViewIOS> _omniboxViewIOS;
   /// Omnibox edit model. Should only be used for autocomplete interactions.
   raw_ptr<OmniboxEditModel> _omniboxEditModel;
 
@@ -59,12 +56,11 @@
   metrics::OmniboxEventProto::OmniboxPosition _preferredOmniboxPosition;
 }
 
-- (instancetype)initWithOmniboxController:(OmniboxController*)omniboxController
-                           omniboxViewIOS:(OmniboxViewIOS*)omniboxViewIOS {
+- (instancetype)initWithOmniboxController:
+    (OmniboxController*)omniboxController {
   self = [super init];
   if (self) {
     _omniboxController = omniboxController;
-    _omniboxViewIOS = omniboxViewIOS;
     _omniboxEditModel = omniboxController->edit_model();
 
     _preferredOmniboxPosition = metrics::OmniboxEventProto::UNKNOWN_POSITION;
@@ -86,7 +82,6 @@
   _autocompleteResultWrapper = nil;
   _omniboxEditModel = nullptr;
   _omniboxController = nullptr;
-  _omniboxViewIOS = nullptr;
 }
 
 - (AutocompleteController*)autocompleteController {
@@ -212,9 +207,7 @@
     fill_into_edit.append(1, ' ');
   }
 
-  if (_omniboxViewIOS) {
-    _omniboxViewIOS->OnSelectedMatchForAppending(fill_into_edit);
-  }
+  [self.omniboxTextController refineWithText:fill_into_edit];
 }
 
 - (void)selectMatchForDeletion:(const AutocompleteMatch&)match {
diff --git a/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller_unittest.mm b/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller_unittest.mm
index 38a3d32..69b73b5 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller_unittest.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_autocomplete_controller_unittest.mm
@@ -126,8 +126,7 @@
         OCMProtocolMock(@protocol(OmniboxAutocompleteControllerDelegate));
 
     controller_ = [[OmniboxAutocompleteController alloc]
-        initWithOmniboxController:omnibox_controller_.get()
-                   omniboxViewIOS:nullptr];
+        initWithOmniboxController:omnibox_controller_.get()];
     controller_.delegate = controller_delegate_;
   }
 
diff --git a/ios/chrome/browser/omnibox/model/omnibox_text_controller.h b/ios/chrome/browser/omnibox/model/omnibox_text_controller.h
index 5f46ff8..42abd462 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_text_controller.h
+++ b/ios/chrome/browser/omnibox/model/omnibox_text_controller.h
@@ -123,6 +123,9 @@
 /// Hides the keyboard.
 - (void)hideKeyboard;
 
+/// Refines omnibox content with `text`.
+- (void)refineWithText:(const std::u16string&)text;
+
 #pragma mark - Private event
 // Events that are private. Removed from header after refactoring
 // (crbug.com/390409559). Since these methods should be private, comments are in
diff --git a/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm b/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm
index d39a5bf..81c770f 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm
@@ -365,8 +365,26 @@
 }
 
 - (void)onDeleteBackward {
-  if (_omniboxViewIOS) {
-    _omniboxViewIOS->OnDeleteBackward();
+  OmniboxTextFieldIOS* textField = self.textField;
+  if (textField.text.length == 0) {
+    // If the user taps backspace while the pre-edit text is showing,
+    // OnWillChange is invoked before this method and sets the text to an empty
+    // string, so use the `clearingPreEditText` to determine if the chip should
+    // be cleared or not.
+    if ([textField clearingPreEditText]) {
+      // In the case where backspace is tapped while in pre-edit mode,
+      // OnWillChange is called but OnDidChange is never called so ensure the
+      // clearingPreEditText flag is set to false again.
+      [textField setClearingPreEditText:NO];
+      // Explicitly set the input-in-progress flag. Normally this is set via
+      // in model()->OnAfterPossibleChange, but in this case the text has been
+      // set to the empty string by OnWillChange so when OnAfterPossibleChange
+      // checks if the text has changed it does not see any difference so it
+      // never sets the input-in-progress flag.
+      if (_omniboxEditModel) {
+        _omniboxEditModel->SetInputInProgress(YES);
+      }
+    }
   }
 }
 
@@ -399,6 +417,24 @@
   }
 }
 
+- (void)refineWithText:(const std::u16string&)text {
+  OmniboxTextFieldIOS* textField = self.textField;
+  if (!_omniboxViewIOS) {
+    return;
+  }
+  // Exit preedit state and append the match. Refocus if necessary.
+  [textField exitPreEditState];
+  _omniboxViewIOS->SetUserText(text);
+  // Calling setText: does not trigger UIControlEventEditingChanged, so
+  // trigger that manually.
+  [textField sendActionsForControlEvents:UIControlEventEditingChanged];
+  [textField becomeFirstResponder];
+  if (@available(iOS 17, *)) {
+    // Set the caret pos to the end of the text (crbug.com/331622199).
+    _omniboxViewIOS->SetCaretPos(text.length());
+  }
+}
+
 #pragma mark - Private
 
 /// Previews `suggestion` in the Omnibox. Called when a suggestion is
diff --git a/ios/chrome/browser/omnibox/ui_bundled/omnibox_view_ios.h b/ios/chrome/browser/omnibox/ui_bundled/omnibox_view_ios.h
index 37736dced..65f5dd5 100644
--- a/ios/chrome/browser/omnibox/ui_bundled/omnibox_view_ios.h
+++ b/ios/chrome/browser/omnibox/ui_bundled/omnibox_view_ios.h
@@ -89,21 +89,10 @@
   // Called after the Omnibox text field changes. `processing_user_input` holds
   // whether the change was user-initiated or programmatic.
   void OnDidChange(bool processing_user_input);
-  // Called when the backspace button is pressed in the Omnibox text field.
-  void OnDeleteBackward();
   // Called when autocomplete text is accepted. (e.g. tap on autocomplete text,
   // tap on left/right arrow key).
   void OnAcceptAutocomplete();
 
-  // OmniboxAutocompleteController interactions.
-  void OnSelectedMatchForAppending(const std::u16string& str);
-
-  // Focus the omnibox field.  This is used when the omnibox popup copies a
-  // search query to the omnibox so the user can modify it further.
-  // This does not affect the popup state and is a NOOP if the omnibox is
-  // already focused.
-  void FocusOmnibox();
-
  protected:
   int GetOmniboxTextLength() const override;
   void EmphasizeURLComponents() override {}
diff --git a/ios/chrome/browser/omnibox/ui_bundled/omnibox_view_ios.mm b/ios/chrome/browser/omnibox/ui_bundled/omnibox_view_ios.mm
index 7911115c..82dd9c38 100644
--- a/ios/chrome/browser/omnibox/ui_bundled/omnibox_view_ios.mm
+++ b/ios/chrome/browser/omnibox/ui_bundled/omnibox_view_ios.mm
@@ -347,56 +347,11 @@
   OnBeforePossibleChange();
 }
 
-void OmniboxViewIOS::OnDeleteBackward() {
-  if (field_.text.length == 0) {
-    // If the user taps backspace while the pre-edit text is showing,
-    // OnWillChange is invoked before this method and sets the text to an empty
-    // string, so use the `clearingPreEditText` to determine if the chip should
-    // be cleared or not.
-    if ([field_ clearingPreEditText]) {
-      // In the case where backspace is tapped while in pre-edit mode,
-      // OnWillChange is called but OnDidChange is never called so ensure the
-      // clearingPreEditText flag is set to false again.
-      [field_ setClearingPreEditText:NO];
-      // Explicitly set the input-in-progress flag. Normally this is set via
-      // in model()->OnAfterPossibleChange, but in this case the text has been
-      // set to the empty string by OnWillChange so when OnAfterPossibleChange
-      // checks if the text has changed it does not see any difference so it
-      // never sets the input-in-progress flag.
-      if (model()) {
-        model()->SetInputInProgress(YES);
-      }
-    }
-  }
-}
-
 void OmniboxViewIOS::OnAcceptAutocomplete() {
   current_selection_ = [field_ selectedNSRange];
   OnDidChange(/*processing_user_event=*/true);
 }
 
-void OmniboxViewIOS::FocusOmnibox() {
-  [field_ becomeFirstResponder];
-}
-
 int OmniboxViewIOS::GetOmniboxTextLength() const {
   return [field_ displayedText].length;
 }
-
-#pragma mark - OmniboxAutocompleteController interactions
-
-void OmniboxViewIOS::OnSelectedMatchForAppending(const std::u16string& str) {
-  // Exit preedit state and append the match. Refocus if necessary.
-  if ([field_ isPreEditing]) {
-    [field_ exitPreEditState];
-  }
-  this->SetUserText(str);
-  // Calling setText: does not trigger UIControlEventEditingChanged, so
-  // trigger that manually.
-  [field_ sendActionsForControlEvents:UIControlEventEditingChanged];
-  this->FocusOmnibox();
-  if (@available(iOS 17, *)) {
-    // Set the caret pos to the end of the text (crbug.com/331622199).
-    this->SetCaretPos(str.length());
-  }
-}
diff --git a/ios/chrome/browser/popup_menu/ui_bundled/overflow_menu/overflow_menu_mediator.mm b/ios/chrome/browser/popup_menu/ui_bundled/overflow_menu/overflow_menu_mediator.mm
index 3852a27..3e01318 100644
--- a/ios/chrome/browser/popup_menu/ui_bundled/overflow_menu/overflow_menu_mediator.mm
+++ b/ios/chrome/browser/popup_menu/ui_bundled/overflow_menu/overflow_menu_mediator.mm
@@ -743,6 +743,8 @@
 }
 
 - (OverflowMenuAction*)openReaderModeAction {
+  // TODO(crbug.com/409935686): Dynamically update the Reader mode overflow menu
+  // string to "Exit Reader Mode" if Reader mode is already enabled.
   __weak __typeof(self) weakSelf = self;
   return [self
       createOverflowMenuActionWithNameID:IDS_IOS_TOOLS_MENU_READER_MODE
@@ -753,7 +755,7 @@
                          accessibilityID:kToolsMenuOpenReaderMode
                             hideItemText:nil
                                  handler:^{
-                                   [weakSelf startReaderMode];
+                                   [weakSelf toggleReaderMode];
                                  }];
 }
 
@@ -2358,10 +2360,10 @@
       showSetTabReminderUI:SetTabReminderEntryPoint::kOverflowMenu];
 }
 
-// Opens the Reader mode UI.
-- (void)startReaderMode {
+// Opens or closes the Reader mode UI.
+- (void)toggleReaderMode {
   [self dismissMenu];
-  [self.readerModeHandler showReaderMode];
+  [self.readerModeHandler toggleReaderMode];
 }
 
 #pragma mark - Destinations Handlers
diff --git a/ios/chrome/browser/reader_mode/model/OWNERS b/ios/chrome/browser/reader_mode/OWNERS
similarity index 100%
rename from ios/chrome/browser/reader_mode/model/OWNERS
rename to ios/chrome/browser/reader_mode/OWNERS
diff --git a/ios/chrome/browser/reader_mode/coordinator/BUILD.gn b/ios/chrome/browser/reader_mode/coordinator/BUILD.gn
new file mode 100644
index 0000000..fc04fd34
--- /dev/null
+++ b/ios/chrome/browser/reader_mode/coordinator/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("coordinator") {
+  sources = [
+    "reader_mode_coordinator.h",
+    "reader_mode_coordinator.mm",
+    "reader_mode_mediator.h",
+    "reader_mode_mediator.mm",
+  ]
+  deps = [
+    "//ios/chrome/browser/reader_mode/model",
+    "//ios/chrome/browser/reader_mode/ui",
+    "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
+    "//ios/chrome/browser/shared/model/browser",
+    "//ios/chrome/browser/shared/model/profile",
+    "//ios/chrome/browser/shared/model/web_state_list",
+    "//ios/chrome/browser/tabs/model",
+    "//ios/web/public",
+  ]
+}
diff --git a/ios/chrome/browser/reader_mode/coordinator/reader_mode_coordinator.h b/ios/chrome/browser/reader_mode/coordinator/reader_mode_coordinator.h
new file mode 100644
index 0000000..d16c57a2
--- /dev/null
+++ b/ios/chrome/browser/reader_mode/coordinator/reader_mode_coordinator.h
@@ -0,0 +1,15 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_READER_MODE_COORDINATOR_READER_MODE_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_READER_MODE_COORDINATOR_READER_MODE_COORDINATOR_H_
+
+#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
+
+// Coordinator for the Reader mode UI.
+@interface ReaderModeCoordinator : ChromeCoordinator
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_READER_MODE_COORDINATOR_READER_MODE_COORDINATOR_H_
diff --git a/ios/chrome/browser/reader_mode/coordinator/reader_mode_coordinator.mm b/ios/chrome/browser/reader_mode/coordinator/reader_mode_coordinator.mm
new file mode 100644
index 0000000..a042b6f4
--- /dev/null
+++ b/ios/chrome/browser/reader_mode/coordinator/reader_mode_coordinator.mm
@@ -0,0 +1,37 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/reader_mode/coordinator/reader_mode_coordinator.h"
+
+#import "ios/chrome/browser/reader_mode/coordinator/reader_mode_mediator.h"
+#import "ios/chrome/browser/reader_mode/ui/reader_mode_view_controller.h"
+#import "ios/chrome/browser/shared/model/browser/browser.h"
+#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
+
+@interface ReaderModeCoordinator ()
+@end
+
+@implementation ReaderModeCoordinator {
+  ReaderModeViewController* _viewController;
+  ReaderModeMediator* _mediator;
+}
+
+- (void)start {
+  _viewController = [[ReaderModeViewController alloc] init];
+  _mediator = [[ReaderModeMediator alloc]
+      initWithWebState:self.browser->GetWebStateList()->GetActiveWebState()];
+  _mediator.consumer = _viewController;
+  [self.baseViewController addChildViewController:_viewController];
+  [_viewController didMoveToParentViewController:self.baseViewController];
+}
+
+- (void)stop {
+  [_mediator disconnect];
+  _mediator = nil;
+  [_viewController willMoveToParentViewController:nil];
+  [_viewController removeFromParentViewController];
+  _viewController = nil;
+}
+
+@end
diff --git a/ios/chrome/browser/reader_mode/coordinator/reader_mode_mediator.h b/ios/chrome/browser/reader_mode/coordinator/reader_mode_mediator.h
new file mode 100644
index 0000000..7ac752e0
--- /dev/null
+++ b/ios/chrome/browser/reader_mode/coordinator/reader_mode_mediator.h
@@ -0,0 +1,31 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_READER_MODE_COORDINATOR_READER_MODE_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_READER_MODE_COORDINATOR_READER_MODE_MEDIATOR_H_
+
+#import <Foundation/Foundation.h>
+
+#import "base/memory/raw_ptr.h"
+#import "ios/chrome/browser/reader_mode/ui/reader_mode_consumer.h"
+
+namespace web {
+class WebState;
+}
+
+// Mediator for the Reader mode UI.
+@interface ReaderModeMediator : NSObject
+
+@property(nonatomic, weak) id<ReaderModeConsumer> consumer;
+
+// Initializes the mediator using `webState` as the source WebState from which
+// Reader mode content needs to be extracted.
+- (instancetype)initWithWebState:(raw_ptr<web::WebState>)webState;
+
+// Disconnects the mediator from the model layer.
+- (void)disconnect;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_READER_MODE_COORDINATOR_READER_MODE_MEDIATOR_H_
diff --git a/ios/chrome/browser/reader_mode/coordinator/reader_mode_mediator.mm b/ios/chrome/browser/reader_mode/coordinator/reader_mode_mediator.mm
new file mode 100644
index 0000000..07b06b2
--- /dev/null
+++ b/ios/chrome/browser/reader_mode/coordinator/reader_mode_mediator.mm
@@ -0,0 +1,44 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/reader_mode/coordinator/reader_mode_mediator.h"
+
+#import "ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.h"
+#import "ios/web/public/web_state.h"
+
+@implementation ReaderModeMediator {
+  base::WeakPtr<web::WebState> _sourceWebState;
+}
+
+#pragma mark - Initialization
+
+- (instancetype)initWithWebState:(raw_ptr<web::WebState>)webState {
+  self = [super init];
+  if (self) {
+    CHECK(webState);
+    // TODO(crbug.com/409940117): Ensure the UI reacts to a change in active
+    // WebState e.g. new active WebState or no active WebState.
+    _sourceWebState = webState->GetWeakPtr();
+  }
+  return self;
+}
+
+#pragma mark - Properties
+
+- (void)setConsumer:(id<ReaderModeConsumer>)consumer {
+  CHECK(consumer);
+  _consumer = consumer;
+  ReaderModeTabHelper* tabHelper =
+      ReaderModeTabHelper::FromWebState(_sourceWebState.get());
+  CHECK(tabHelper);
+  [self.consumer setContentView:tabHelper->GetReaderModeContentView()];
+}
+
+#pragma mark - Public
+
+- (void)disconnect {
+  _sourceWebState = nil;
+}
+
+@end
diff --git a/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.h b/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.h
index ec5ca8f..1352e908 100644
--- a/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.h
+++ b/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.h
@@ -12,6 +12,7 @@
 #import "ios/web/public/web_state_observer.h"
 #import "ios/web/public/web_state_user_data.h"
 
+@protocol ReaderModeCommands;
 @protocol SnackbarCommands;
 
 // Observes changes to the web state to perform reader mode operations.
@@ -24,8 +25,19 @@
 
   ~ReaderModeTabHelper() override;
 
+  // Returns whether Reader mode is active in the current tab. If so, the Reader
+  // mode UI should be presented.
+  bool IsActive() const;
+  // Activates/deactivates Reader mode in the current tab.
+  void SetActive(bool active);
+  // Returns the Reader mode content view. A precondition for calling this
+  // method is for Reader mode to be active in this tab.
+  UIView* GetReaderModeContentView();
+
   // Sets the snackbar handler.
   void SetSnackbarHandler(id<SnackbarCommands> snackbar_handler);
+  // Sets the reader mode handler.
+  void SetReaderModeHandler(id<ReaderModeCommands> reader_mode_handler);
 
   // Processes the result of the Reader Mode heuristic trigger that was run on
   // the `url` content.
@@ -42,6 +54,7 @@
       web::WebState* web_state,
       web::PageLoadCompletionStatus load_completion_status) override;
   void WebStateDestroyed(web::WebState* web_state) override;
+  void WasHidden(web::WebState* web_state) override;
 
   // Trigger the heuristic to determine reader mode eligibility.
   void TriggerReaderModeHeuristic();
@@ -58,7 +71,12 @@
                                  base::TimeTicks start_time,
                                  const base::Value* value);
 
+  // Whether Reader mode is active in this tab.
+  bool active_ = false;
+  // WebState used to render the Reader mode content.
+  std::unique_ptr<web::WebState> reader_mode_web_state_;
   id<SnackbarCommands> snackbar_handler_;
+  id<ReaderModeCommands> reader_mode_handler_;
   base::TimeDelta heuristic_latency_;
   raw_ptr<web::WebState> web_state_ = nullptr;
   base::OneShotTimer trigger_reader_mode_timer_;
diff --git a/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.mm b/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.mm
index f923a72..b398ee7 100644
--- a/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.mm
+++ b/ios/chrome/browser/reader_mode/model/reader_mode_tab_helper.mm
@@ -18,10 +18,13 @@
 #import "ios/chrome/browser/reader_mode/model/features.h"
 #import "ios/chrome/browser/reader_mode/model/reader_mode_java_script_feature.h"
 #import "ios/chrome/browser/shared/model/profile/profile_ios.h"
+#import "ios/chrome/browser/shared/public/commands/reader_mode_commands.h"
 #import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
 #import "ios/chrome/browser/shared/public/features/system_flags.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
 #import "ios/web/public/navigation/navigation_context.h"
+#import "ios/web/public/navigation/navigation_item.h"
+#import "ios/web/public/navigation/navigation_manager.h"
 #import "services/metrics/public/cpp/ukm_builders.h"
 #import "third_party/dom_distiller_js/dom_distiller.pb.h"
 #import "third_party/dom_distiller_js/dom_distiller_json_converter.h"
@@ -162,11 +165,51 @@
 
 ReaderModeTabHelper::~ReaderModeTabHelper() = default;
 
+bool ReaderModeTabHelper::IsActive() const {
+  return active_;
+}
+
+void ReaderModeTabHelper::SetActive(bool active) {
+  if (active == active_) {
+    return;
+  }
+  active_ = active;
+  if (active_) {
+    // If Reader mode is being activated, create the secondary WebState where
+    // the content will be rendered and start distillation.
+    web::WebState::CreateParams create_params = web::WebState::CreateParams(
+        ProfileIOS::FromBrowserState(web_state_->GetBrowserState())
+            ->GetOffTheRecordProfile());
+    reader_mode_web_state_ = web::WebState::Create(create_params);
+    reader_mode_web_state_->SetWebUsageEnabled(true);
+    // TODO(crbug.com/409940117): Decouple heuristic and distillation.
+    TriggerReaderModeHeuristic();
+  } else {
+    // If Reader mode is being deactivated, destroy the secondary WebState and
+    // ensure the Reader mode UI is dismissed.
+    // TODO(crbug.com/409940117): Ensure the UI gracefully handles the
+    // destruction of the Reader mode WebState while its view is inside the view
+    // hierarchy.
+    reader_mode_web_state_.reset();
+    [reader_mode_handler_ hideReaderMode];
+  }
+}
+
+UIView* ReaderModeTabHelper::GetReaderModeContentView() {
+  CHECK(reader_mode_web_state_);
+  return reader_mode_web_state_->GetView();
+}
+
 void ReaderModeTabHelper::SetSnackbarHandler(
     id<SnackbarCommands> snackbar_handler) {
   snackbar_handler_ = snackbar_handler;
 }
 
+void ReaderModeTabHelper::SetReaderModeHandler(
+    id<ReaderModeCommands> reader_mode_handler) {
+  reader_mode_handler_ = reader_mode_handler;
+}
+
 void ReaderModeTabHelper::PageLoaded(
     web::WebState* web_state,
     web::PageLoadCompletionStatus load_completion_status) {
@@ -199,6 +242,10 @@
   if (trigger_reader_mode_timer_.IsRunning()) {
     trigger_reader_mode_timer_.Stop();
   }
+  if (IsReaderModeAvailable()) {
+    // Hide Reader mode.
+    [reader_mode_handler_ hideReaderMode];
+  }
 }
 
 void ReaderModeTabHelper::WebStateDestroyed(web::WebState* web_state) {
@@ -207,6 +254,13 @@
   web_state_ = nullptr;
 }
 
+void ReaderModeTabHelper::WasHidden(web::WebState* web_state) {
+  if (IsReaderModeAvailable()) {
+    // Ensure the Reader mode UI is hidden when the tab is hidden.
+    [reader_mode_handler_ hideReaderMode];
+  }
+}
+
 void ReaderModeTabHelper::HandleReaderModeHeuristicResult(
     const GURL& url,
     ReaderModeHeuristicResult result) {
@@ -346,9 +400,24 @@
     [snackbar_handler_ showSnackbarMessage:message];
   }
 
-  if (IsReaderModeAvailable() && is_distillable_page) {
-    web_state_->LoadSimulatedRequest(
-        web_state_->GetVisibleURL(),
-        base::SysUTF8ToNSString(distiller_result->distilled_content().html()));
+  if (IsReaderModeAvailable()) {
+    if (is_distillable_page) {
+      // `LoadData` requires an already committed navigation item.
+      std::vector<std::unique_ptr<web::NavigationItem>> navigation_items;
+      navigation_items.push_back(web::NavigationItem::Create());
+      reader_mode_web_state_->GetNavigationManager()->Restore(
+          0, std::move(navigation_items));
+      reader_mode_web_state_->GetNavigationManager()->LoadIfNecessary();
+      // Load the Reader mode content in the Reader mode WebState.
+      const std::string content = distiller_result->distilled_content().html();
+      reader_mode_web_state_->LoadData(
+          [NSData dataWithBytes:content.data() length:content.length()],
+          @"text/html", web_state_->GetLastCommittedURL());
+      // Once the Reader mode content is ready, show the Reader mode UI.
+      [reader_mode_handler_ showReaderMode];
+    } else {
+      // If the page could not be distilled, ensure Reader mode UI is hidden.
+      [reader_mode_handler_ hideReaderMode];
+    }
   }
 }
diff --git a/ios/chrome/browser/reader_mode/ui/BUILD.gn b/ios/chrome/browser/reader_mode/ui/BUILD.gn
new file mode 100644
index 0000000..eab01f4
--- /dev/null
+++ b/ios/chrome/browser/reader_mode/ui/BUILD.gn
@@ -0,0 +1,15 @@
+# Copyright 2025 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("ui") {
+  sources = [
+    "reader_mode_consumer.h",
+    "reader_mode_view_controller.h",
+    "reader_mode_view_controller.mm",
+  ]
+  deps = [
+    "//ios/chrome/browser/shared/ui/util",
+    "//ios/chrome/common/ui/util",
+  ]
+}
diff --git a/ios/chrome/browser/reader_mode/ui/reader_mode_consumer.h b/ios/chrome/browser/reader_mode/ui/reader_mode_consumer.h
new file mode 100644
index 0000000..7650a34b
--- /dev/null
+++ b/ios/chrome/browser/reader_mode/ui/reader_mode_consumer.h
@@ -0,0 +1,18 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_READER_MODE_UI_READER_MODE_CONSUMER_H_
+#define IOS_CHROME_BROWSER_READER_MODE_UI_READER_MODE_CONSUMER_H_
+
+#import <UIKit/UIKit.h>
+
+// Consumer for the Reader mode UI.
+@protocol ReaderModeConsumer <NSObject>
+
+// Sets the Reader mode content view.
+- (void)setContentView:(UIView*)contentView;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_READER_MODE_UI_READER_MODE_CONSUMER_H_
diff --git a/ios/chrome/browser/reader_mode/ui/reader_mode_view_controller.h b/ios/chrome/browser/reader_mode/ui/reader_mode_view_controller.h
new file mode 100644
index 0000000..40454a4
--- /dev/null
+++ b/ios/chrome/browser/reader_mode/ui/reader_mode_view_controller.h
@@ -0,0 +1,17 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_READER_MODE_UI_READER_MODE_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_READER_MODE_UI_READER_MODE_VIEW_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+#import "ios/chrome/browser/reader_mode/ui/reader_mode_consumer.h"
+
+// View controller for displaying the Reader mode content.
+@interface ReaderModeViewController : UIViewController <ReaderModeConsumer>
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_READER_MODE_UI_READER_MODE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/reader_mode/ui/reader_mode_view_controller.mm b/ios/chrome/browser/reader_mode/ui/reader_mode_view_controller.mm
new file mode 100644
index 0000000..d123e9c
--- /dev/null
+++ b/ios/chrome/browser/reader_mode/ui/reader_mode_view_controller.mm
@@ -0,0 +1,56 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/reader_mode/ui/reader_mode_view_controller.h"
+
+#import "ios/chrome/browser/shared/ui/util/named_guide.h"
+#import "ios/chrome/common/ui/util/constraints_ui_util.h"
+
+@interface ReaderModeViewController ()
+
+@end
+
+@implementation ReaderModeViewController {
+  UIView* _contentView;
+}
+
+#pragma mark - UIViewController
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+  self.view.translatesAutoresizingMaskIntoConstraints = NO;
+}
+
+- (void)willMoveToParentViewController:(UIViewController*)parent {
+  if (!parent) {
+    [self.view removeFromSuperview];
+  }
+  [super willMoveToParentViewController:parent];
+}
+
+- (void)didMoveToParentViewController:(UIViewController*)parent {
+  [super didMoveToParentViewController:parent];
+  if (parent) {
+    [parent.view addSubview:self.view];
+    AddSameConstraints([NamedGuide guideWithName:kContentAreaGuide
+                                            view:parent.view],
+                       self.view);
+  }
+}
+
+#pragma mark - ReaderModeConsumer
+
+- (void)setContentView:(UIView*)contentView {
+  if (_contentView) {
+    [_contentView removeFromSuperview];
+  }
+  _contentView = contentView;
+  if (_contentView) {
+    _contentView.translatesAutoresizingMaskIntoConstraints = NO;
+    [self.view addSubview:_contentView];
+    AddSameConstraints(self.view, _contentView);
+  }
+}
+
+@end
diff --git a/ios/chrome/browser/reading_list/ui_bundled/OWNERS b/ios/chrome/browser/reading_list/ui_bundled/OWNERS
index 9e62e42..4a03091 100644
--- a/ios/chrome/browser/reading_list/ui_bundled/OWNERS
+++ b/ios/chrome/browser/reading_list/ui_bundled/OWNERS
@@ -1,4 +1,2 @@
 gambard@chromium.org
 olivierrobin@chromium.org
-
-per-file *badge_view*=edchin@chromium.org
diff --git a/ios/chrome/browser/screen_time/model/OWNERS b/ios/chrome/browser/screen_time/model/OWNERS
deleted file mode 100644
index 3baf3c0..0000000
--- a/ios/chrome/browser/screen_time/model/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-edchin@chromium.org
diff --git a/ios/chrome/browser/screen_time/ui_bundled/OWNERS b/ios/chrome/browser/screen_time/ui_bundled/OWNERS
index 488a19a3..49ba79e 100644
--- a/ios/chrome/browser/screen_time/ui_bundled/OWNERS
+++ b/ios/chrome/browser/screen_time/ui_bundled/OWNERS
@@ -1,2 +1 @@
-edchin@chromium.org
 michaeldo@chromium.org
diff --git a/ios/chrome/browser/shared/coordinator/scene/OWNERS b/ios/chrome/browser/shared/coordinator/scene/OWNERS
index b98e1d4..f49ed38 100644
--- a/ios/chrome/browser/shared/coordinator/scene/OWNERS
+++ b/ios/chrome/browser/shared/coordinator/scene/OWNERS
@@ -1,2 +1 @@
-edchin@chromium.org
 marq@chromium.org
diff --git a/ios/chrome/browser/shared/public/commands/OWNERS b/ios/chrome/browser/shared/public/commands/OWNERS
index a58e45d..27e97ca 100644
--- a/ios/chrome/browser/shared/public/commands/OWNERS
+++ b/ios/chrome/browser/shared/public/commands/OWNERS
@@ -1,4 +1,3 @@
-edchin@chromium.org
 gambard@chromium.org
 marq@chromium.org
 rohitrao@chromium.org
diff --git a/ios/chrome/browser/shared/public/commands/reader_mode_commands.h b/ios/chrome/browser/shared/public/commands/reader_mode_commands.h
index c59890d..42ff9af150 100644
--- a/ios/chrome/browser/shared/public/commands/reader_mode_commands.h
+++ b/ios/chrome/browser/shared/public/commands/reader_mode_commands.h
@@ -14,6 +14,9 @@
 // Hides the Reader mode UI.
 - (void)hideReaderMode;
 
+// Shows the Reader mode UI if currently inactive and vice-versa.
+- (void)toggleReaderMode;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_READER_MODE_COMMANDS_H_
diff --git a/ios/chrome/browser/side_swipe/ui_bundled/OWNERS b/ios/chrome/browser/side_swipe/ui_bundled/OWNERS
index 1bcf1637..330cb31 100644
--- a/ios/chrome/browser/side_swipe/ui_bundled/OWNERS
+++ b/ios/chrome/browser/side_swipe/ui_bundled/OWNERS
@@ -1,2 +1 @@
-edchin@chromium.org
 justincohen@chromium.org
diff --git a/ios/chrome/browser/signin/model/BUILD.gn b/ios/chrome/browser/signin/model/BUILD.gn
index c6b5e957..6612719 100644
--- a/ios/chrome/browser/signin/model/BUILD.gn
+++ b/ios/chrome/browser/signin/model/BUILD.gn
@@ -453,6 +453,7 @@
     "resized_avatar_cache_unittest.mm",
     "signin_profile_info_updater_unittest.mm",
     "signin_util_unittest.mm",
+    "system_account_updater_unittests.mm",
     "system_identity_manager_unittest.mm",
   ]
   deps = [
@@ -499,6 +500,8 @@
     "//ios/chrome/browser/sync/model",
     "//ios/chrome/browser/sync/model:test_support",
     "//ios/chrome/browser/web/model",
+    "//ios/chrome/browser/widget_kit/model:features",
+    "//ios/chrome/common/app_group",
     "//ios/chrome/test:test_support",
     "//ios/net",
     "//ios/web/common",
diff --git a/ios/chrome/browser/signin/model/DEPS b/ios/chrome/browser/signin/model/DEPS
index 70927b1..94f7cfe 100644
--- a/ios/chrome/browser/signin/model/DEPS
+++ b/ios/chrome/browser/signin/model/DEPS
@@ -27,6 +27,9 @@
     "+ios/chrome/browser/widget_kit/model/features.h",
     "+ios/chrome/browser/widget_kit/model/model_swift.h",
   ],
+  "^system_account_updater_unittests.mm": [
+    "+ios/chrome/browser/widget_kit/model/features.h",
+  ],
   "^authentication_service.mm": [
     "+ios/chrome/browser/widget_kit/model/features.h",
     "+ios/chrome/browser/widget_kit/model/model_swift.h",
diff --git a/ios/chrome/browser/signin/model/system_account_updater_unittests.mm b/ios/chrome/browser/signin/model/system_account_updater_unittests.mm
new file mode 100644
index 0000000..73380dc
--- /dev/null
+++ b/ios/chrome/browser/signin/model/system_account_updater_unittests.mm
@@ -0,0 +1,188 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/signin/model/system_account_updater.h"
+
+#import <UIKit/UIKit.h>
+
+#import "base/test/task_environment.h"
+#import "ios/chrome/browser/shared/model/application_context/application_context.h"
+#import "ios/chrome/browser/signin/model/fake_system_identity.h"
+#import "ios/chrome/browser/signin/model/fake_system_identity_manager.h"
+#import "ios/chrome/browser/widget_kit/model/features.h"
+#import "ios/chrome/common/app_group/app_group_constants.h"
+#import "testing/platform_test.h"
+
+#if BUILDFLAG(ENABLE_WIDGETS_FOR_MIM)
+
+class SystemAccountUpdaterTest : public PlatformTest {
+ public:
+  SystemAccountUpdaterTest() {
+    NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();
+    [shared_defaults removeObjectForKey:app_group::kAccountsOnDevice];
+    [shared_defaults
+        removeObjectForKey:app_group::kSuggestedItemsForMultiprofile];
+    [shared_defaults
+        removeObjectForKey:
+            app_group::kSuggestedItemsLastModificationDateForMultiprofile];
+
+    system_identity_manager_ =
+        FakeSystemIdentityManager::FromSystemIdentityManager(
+            GetApplicationContext()->GetSystemIdentityManager());
+    system_account_updater_ =
+        std::make_unique<SystemAccountUpdater>(system_identity_manager_);
+  }
+
+  ~SystemAccountUpdaterTest() override {
+    system_identity_manager_ = nullptr;
+
+    NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();
+    [shared_defaults removeObjectForKey:app_group::kAccountsOnDevice];
+    [shared_defaults
+        removeObjectForKey:app_group::kSuggestedItemsForMultiprofile];
+    [shared_defaults
+        removeObjectForKey:
+            app_group::kSuggestedItemsLastModificationDateForMultiprofile];
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<SystemAccountUpdater> system_account_updater_;
+  raw_ptr<FakeSystemIdentityManager> system_identity_manager_;
+};
+
+// Tests that OnIdentityListChangedis correctly updates key
+// 'app_group::kAccountsOnDevice'.
+TEST_F(SystemAccountUpdaterTest, OnIdentityListChanged) {
+  NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();
+  EXPECT_EQ([[shared_defaults objectForKey:app_group::kAccountsOnDevice] count],
+            0u);
+
+  // Add 'fakeIdentity1' to the identity list.
+  FakeSystemIdentity* fake_identity = [FakeSystemIdentity fakeIdentity1];
+  system_identity_manager_->AddIdentity(fake_identity);
+
+  {
+    NSDictionary* accounts_on_device =
+        [shared_defaults objectForKey:app_group::kAccountsOnDevice];
+    EXPECT_EQ([accounts_on_device count], 1u);
+    EXPECT_TRUE(
+        [[accounts_on_device allKeys] containsObject:fake_identity.gaiaID]);
+    EXPECT_EQ([[accounts_on_device valueForKey:fake_identity.gaiaID]
+                  valueForKey:@"email"],
+              fake_identity.userEmail);
+  }
+
+  // Add 'fakeIdentity2' to the identity list.
+  FakeSystemIdentity* fake_identity_2 = [FakeSystemIdentity fakeIdentity2];
+  system_identity_manager_->AddIdentity(fake_identity_2);
+
+  {
+    NSDictionary* accounts_on_device =
+        [shared_defaults objectForKey:app_group::kAccountsOnDevice];
+    EXPECT_EQ([accounts_on_device count], 2u);
+    EXPECT_TRUE(
+        [[accounts_on_device allKeys] containsObject:fake_identity_2.gaiaID]);
+    EXPECT_EQ([[accounts_on_device valueForKey:fake_identity_2.gaiaID]
+                  valueForKey:@"email"],
+              fake_identity_2.userEmail);
+  }
+
+  // Remove 'fakeIdentity' from the identity list.
+  system_identity_manager_->ForgetIdentity(fake_identity, base::DoNothing());
+  system_identity_manager_->WaitForServiceCallbacksToComplete();
+
+  {
+    NSDictionary* accounts_on_device =
+        [shared_defaults objectForKey:app_group::kAccountsOnDevice];
+    EXPECT_EQ([accounts_on_device count], 1u);
+  }
+}
+
+// Test that data from kSuggestedItemsForMultiprofile is
+// correctly removed when an account is removed from device.
+TEST_F(SystemAccountUpdaterTest, TestSuggestedItems) {
+  NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();
+  EXPECT_EQ([[shared_defaults
+                objectForKey:app_group::kSuggestedItemsForMultiprofile] count],
+            0u);
+
+  FakeSystemIdentity* fake_identity = [FakeSystemIdentity fakeIdentity1];
+
+  // Add fake data about fakeIdentity1 to kSuggestedItemsForMultiprofile.
+  NSMutableDictionary* fake_info = [NSMutableDictionary dictionary];
+  [fake_info setObject:@"test_info" forKey:fake_identity.gaiaID];
+  [fake_info setObject:@"test_info" forKey:app_group::kDefaultAccount];
+
+  [shared_defaults setObject:fake_info
+                      forKey:app_group::kSuggestedItemsForMultiprofile];
+  [shared_defaults
+      setObject:fake_info
+         forKey:app_group::kSuggestedItemsLastModificationDateForMultiprofile];
+
+  // Add 'fakeIdentity1' to the identity list.
+  system_identity_manager_->AddIdentity(fake_identity);
+  {
+    NSDictionary* items = [shared_defaults
+        objectForKey:app_group::kSuggestedItemsForMultiprofile];
+    EXPECT_TRUE([[items allKeys] containsObject:fake_identity.gaiaID]);
+  }
+  // Remove 'fakeIdentity' from the identity list.
+  system_identity_manager_->ForgetIdentity(fake_identity, base::DoNothing());
+  system_identity_manager_->WaitForServiceCallbacksToComplete();
+
+  {
+    NSDictionary* items = [shared_defaults
+        objectForKey:app_group::kSuggestedItemsForMultiprofile];
+    EXPECT_TRUE([[items allKeys] containsObject:app_group::kDefaultAccount]);
+    EXPECT_FALSE([[items allKeys] containsObject:fake_identity.gaiaID]);
+  }
+}
+
+// Test that data from kSuggestedItemsLastModificationDateForMultiprofile is
+// correctly removed when an account is removed from device.
+TEST_F(SystemAccountUpdaterTest, TestSuggestedItemsLastModificationDate) {
+  NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults();
+  EXPECT_EQ(
+      [[shared_defaults
+          objectForKey:app_group::
+                           kSuggestedItemsLastModificationDateForMultiprofile]
+          count],
+      0u);
+
+  FakeSystemIdentity* fake_identity = [FakeSystemIdentity fakeIdentity1];
+
+  // Add fake data about fakeIdentity1 to kSuggestedItemsForMultiprofile.
+  NSMutableDictionary* fake_info = [NSMutableDictionary dictionary];
+  [fake_info setObject:@"test_info" forKey:fake_identity.gaiaID];
+  [fake_info setObject:@"test_info" forKey:app_group::kDefaultAccount];
+
+  [shared_defaults setObject:fake_info
+                      forKey:app_group::kSuggestedItemsForMultiprofile];
+  [shared_defaults
+      setObject:fake_info
+         forKey:app_group::kSuggestedItemsLastModificationDateForMultiprofile];
+
+  // Add 'fakeIdentity1' to the identity list.
+  system_identity_manager_->AddIdentity(fake_identity);
+  {
+    NSDictionary* items = [shared_defaults
+        objectForKey:app_group::
+                         kSuggestedItemsLastModificationDateForMultiprofile];
+    EXPECT_TRUE([[items allKeys] containsObject:fake_identity.gaiaID]);
+  }
+  // Remove 'fakeIdentity' from the identity list.
+  system_identity_manager_->ForgetIdentity(fake_identity, base::DoNothing());
+  system_identity_manager_->WaitForServiceCallbacksToComplete();
+
+  {
+    NSDictionary* items = [shared_defaults
+        objectForKey:app_group::
+                         kSuggestedItemsLastModificationDateForMultiprofile];
+    EXPECT_TRUE([[items allKeys] containsObject:app_group::kDefaultAccount]);
+    EXPECT_FALSE([[items allKeys] containsObject:fake_identity.gaiaID]);
+  }
+}
+
+#endif
diff --git a/ios/chrome/browser/snapshots/model/OWNERS b/ios/chrome/browser/snapshots/model/OWNERS
index 40c5684..69874391 100644
--- a/ios/chrome/browser/snapshots/model/OWNERS
+++ b/ios/chrome/browser/snapshots/model/OWNERS
@@ -1,3 +1,2 @@
 asamidoi@chromium.org
-edchin@chromium.org
 justincohen@chromium.org
diff --git a/ios/chrome/common/ui/util/OWNERS b/ios/chrome/common/ui/util/OWNERS
deleted file mode 100644
index 709cc339..0000000
--- a/ios/chrome/common/ui/util/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file pointer_interaction_util*=edchin@chromium.org
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
index 0d6a60d..9da7b56 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-02830ff226fb872270681d03b7b11e39bb633e35
\ No newline at end of file
+447330cd4c96d8104c05399b29fdee38d7a49112
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
index 15332271..3d60f2b9 100644
--- a/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeExtensionKeychainInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-24a9dfee1f11071a9f9a5837c3f530f0652cb91b
\ No newline at end of file
+a1d6800ed768d0f3110115f37cce9a11eac35415
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index 6850ba8d..a899480 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-9dcc744b33568ea128161fac9861508b031d1ce5
\ No newline at end of file
+f2eb88084692042f7a319a3c95e9c50764dac8e6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
index c849759..d3d9b81 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-f10bb9323aa28d5cc258fda9f85a82304ebb4e03
\ No newline at end of file
+d5237ddb9a4ece6eb915f9bff57189d5975f85eb
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index 0942f079..501d7762 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-fa681fb3dc1c5909281a2695b34dfaa2d45a4b08
\ No newline at end of file
+a2e30bc6b17737e02a14a0415435e0d071cdbfd0
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
index d98666f..72515c5 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios_asan.zip.sha1
@@ -1 +1 @@
-ace4f54d741eddda75dfae835f21d2a515cc9601
\ No newline at end of file
+92bc57e4459cf48b4a4cc3b2deeba7690407aba0
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
index 5651ef7..ec7ea9b4 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-ccd384a32ba8f4b3c50498dbebdd29b78caad563
\ No newline at end of file
+5e5e4835df1ed01a69a8f1f6150bf88d9486b59e
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
index e1d8cde0..d110f7b 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-c99bcb858e461c9544a9a6dcefcc0ae6d9ec11e4
\ No newline at end of file
+06dfe75ddcc937ad7bb30b666aee763c92276464
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
index 15a1010..9b24135 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-b7a0eafd66dcce87f4f404647a1a3f3b38d95552
\ No newline at end of file
+e11d79df15ba3e1444b36f30ba3081c2794e9faf
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 98b5d9e..b1b2035 100644
--- a/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_extension_keychain_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-400804e722f2ca2e226f62e000380f35413a444e
\ No newline at end of file
+211637af2db6644b28dadd3cf09371b8fdd6e463
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index c2355cf..d05a851 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-07971b3b97bb35c9e9ef0903abdc82f032adce18
\ No newline at end of file
+9ab1338645eddf73f87b0e10f6b7ecb1d8fec9b3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
index 4548866..257eabc 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-d92d32a3b74a7b3e7dd96ec7273cad9f8fa4e9b7
\ No newline at end of file
+02da09dcef9de9123064c271c155360b470fe260
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 630df401..6f2c76d 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-84e92dc855723eba8bc3f81790d677fbf7fa2854
\ No newline at end of file
+825e1cc54ec49e0dbea83012162f80f5cbc1d0c9
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 29b317e..8494c05 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-8d91a7ccd1fc0662ece0b30e9a4824a4cba66cb4
\ No newline at end of file
+f6f8187387ae6ef2a67f35ca1b78ed2e0c3c69f3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index 3b739822..2d78b4a 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-fe89cfc3bb7fb925be7987bfcaf2b316370d0a9b
\ No newline at end of file
+18e4ed53537f641e4e4183e4c01f9295a1126da3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
index d6dcdc5..2fd09c0 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-d92309ad9eab19e7c5916d5976c2415f4b0a1684
\ No newline at end of file
+b783273ba392b64b26c5b7561b916e4a5d9b1325
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index c96c55025..d03a385 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-eeb96de97fb4ced679aa32e6925d086cd25ac975
\ No newline at end of file
+8c157da852389bc912d712067d3ae9d615b6beca
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 47f55a6..186acb0 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-cead191fd27824cbe9cd175dcb2c7dc1ad650cd9
\ No newline at end of file
+2ebbbd1fd23cd96a4ff0df0db5d35d85b0cface6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index 9123e3b5a..1e518fd 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-c6ff32f43471f57b026c3104bb561ad11d7857b5
\ No newline at end of file
+98dcfaad27f42ac9427b293dd3fd86ae7cf73844
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index 11af670a..deb9b65 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-339f75a2071609320b77e09a19f5d1c1b78c95f1
\ No newline at end of file
+042c98caaa3308dd830230f02e17530f4053f3d1
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 5ca78cb..9461c9d 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-8a2841b9ccf1ec4fce984b8e14675f6779d419d8
\ No newline at end of file
+8d59db6e7f61e54b2e12f49a9fdaa967a6286b34
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
index 5a873706..a9a473c 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-599b4b326f7fc0eff88f3fb4eda4d094c71912ae
\ No newline at end of file
+c5e47f0c91640a933adbdab160af8d16aa0f7904
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index 55030af..978c93d 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-6581f24a4280fa9bfcae3a908531a6e9b2180900
\ No newline at end of file
+af9507b00925750d57ab75015b5f2f88c1f25f3c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 26e5b8f..f31950e9 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-0eeb2fb7e1464a67edff438c1a9d56e9b2ddda96
\ No newline at end of file
+5fb941f3a34795aeabf7d298f38135f726635a39
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index b026380..b7ecb1cb 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-078c0f1902ff412565636e4d6dbba57ed49505e3
\ No newline at end of file
+2d0d806484eab78708d5e5fd6a5c5d2d45099404
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
index d04e4c1..4bdb2be 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios_asan.zip.sha1
@@ -1 +1 @@
-3bee984d2376b4f4645c1a35b5b09f20682f192b
\ No newline at end of file
+8ba4305aaf4122517528c997f12ba8dd63775bf2
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 5eb9d65b..2c2ca66 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-3088e716e614601e9c7a207bfd444e43ce5c09bb
\ No newline at end of file
+a53befefbdb07244d3350daf37484f865ef03f35
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
index 4170a3e..f490e39 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator_asan.zip.sha1
@@ -1 +1 @@
-10fb720a1c5235727e81b57616232df0be38f9c3
\ No newline at end of file
+636da13b39efd15ea561d480528eff5aee68f502
\ No newline at end of file
diff --git a/ios/third_party/material_components_ios/src b/ios/third_party/material_components_ios/src
index 7c19c19..91a227c 160000
--- a/ios/third_party/material_components_ios/src
+++ b/ios/third_party/material_components_ios/src
@@ -1 +1 @@
-Subproject commit 7c19c196a74207c263cedffe6eefe14761e69cf8
+Subproject commit 91a227c283a463f49d0b55474f9686b67aa2d9da
diff --git a/ios_internal b/ios_internal
index 2aad56f..5faebbb 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 2aad56f4f3ad2277277e35cfa1ab3ec55937bf81
+Subproject commit 5faebbb3e45a687dae0a0cd09f8f8c69e9897386
diff --git a/media/audio/mac/core_audio_util_mac.cc b/media/audio/mac/core_audio_util_mac.cc
index babc5d7..1c61d2c 100644
--- a/media/audio/mac/core_audio_util_mac.cc
+++ b/media/audio/mac/core_audio_util_mac.cc
@@ -337,12 +337,21 @@
   CFTypeRef value = CFDictionaryGetValue(
       dictionary, CFSTR(kAudioAggregateDeviceIsPrivateKey));
 
-  if (value && CFGetTypeID(value) == CFNumberGetTypeID()) {
+  if (value && CFGetTypeID(value) == CFBooleanGetTypeID()) {
+    CFBooleanRef boolean_ref = static_cast<CFBooleanRef>(value);
+    is_private = CFBooleanGetValue(boolean_ref);
+    base::UmaHistogramBoolean("Media.Audio.Mac.AggregateDeviceIsPrivateBoolean",
+                              is_private);
+  } else if (value && CFGetTypeID(value) == CFNumberGetTypeID()) {
+    // TODO(413285324): Remove this and the UMA if we can confirm that the new
+    // CFBoolean property is covering all cases. Otherwise, just remove the UMA.
     int number = 0;
     if (CFNumberGetValue(reinterpret_cast<CFNumberRef>(value), kCFNumberIntType,
                          &number)) {
       is_private = number != 0;
     }
+    base::UmaHistogramBoolean("Media.Audio.Mac.AggregateDeviceIsPrivateNumber",
+                              is_private);
   }
   CFRelease(dictionary);
 
diff --git a/media/webrtc/helpers_unittests.cc b/media/webrtc/helpers_unittests.cc
index 9c395f9..6b70c38 100644
--- a/media/webrtc/helpers_unittests.cc
+++ b/media/webrtc/helpers_unittests.cc
@@ -142,8 +142,21 @@
 TEST(CreateWebRtcAudioProcessingModuleTest, VerifyNoiseSuppressionSettings) {
   for (bool noise_suppressor_enabled : {true, false}) {
     SCOPED_TRACE(noise_suppressor_enabled);
-    auto config = CreateApmGetConfig(
-        /*settings=*/{.noise_suppression = noise_suppressor_enabled});
+
+    const auto settings =
+        AudioProcessingSettings{.noise_suppression = noise_suppressor_enabled};
+
+    // On iOS-blink and tvOS, AudioProcessingModule is only created when
+    // noise_suppression is enabled. Attempting to create or configure APM when
+    // noise_suppression is false leads to a crash on these platforms.
+    //
+    // This check ensures we only run the test when APM is expected to be
+    // created.
+    if (!settings.NeedWebrtcAudioProcessing()) {
+      continue;
+    }
+
+    auto config = CreateApmGetConfig(settings);
 
     EXPECT_EQ(config.noise_suppression.enabled, noise_suppressor_enabled);
     EXPECT_EQ(config.noise_suppression.level,
diff --git a/media/webrtc/webrtc_features.cc b/media/webrtc/webrtc_features.cc
index 897e5de..f4672d3a 100644
--- a/media/webrtc/webrtc_features.cc
+++ b/media/webrtc/webrtc_features.cc
@@ -37,6 +37,14 @@
              "AllowWgcWindowZeroHz",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+#if BUILDFLAG(IS_WIN)
+// When enabled, instruct WGC to draw a border around the captured
+// window or screen.
+BASE_FEATURE(kWebRtcWgcRequireBorder,
+             "WebRtcWgcRequireBorder",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+#endif
+
 // TODO(crbug.com/40872787): Deactivate the flag gradually before deleting it.
 // When disabled, any WebRTC Audio Processing Module input volume recommendation
 // is ignored and no adjustment takes place.
diff --git a/media/webrtc/webrtc_features.h b/media/webrtc/webrtc_features.h
index a65444f..773f984 100644
--- a/media/webrtc/webrtc_features.h
+++ b/media/webrtc/webrtc_features.h
@@ -27,6 +27,11 @@
 COMPONENT_EXPORT(MEDIA_WEBRTC)
 BASE_DECLARE_FEATURE(kWebRtcAllowWgcWindowZeroHz);
 
+#if BUILDFLAG(IS_WIN)
+COMPONENT_EXPORT(MEDIA_WEBRTC)
+BASE_DECLARE_FEATURE(kWebRtcWgcRequireBorder);
+#endif
+
 COMPONENT_EXPORT(MEDIA_WEBRTC)
 BASE_DECLARE_FEATURE(kWebRtcAllowInputVolumeAdjustment);
 
diff --git a/pdf/pdfium/pdfium_on_demand_searchifier.cc b/pdf/pdfium/pdfium_on_demand_searchifier.cc
index 81b5ed2..7aabb1583 100644
--- a/pdf/pdfium/pdfium_on_demand_searchifier.cc
+++ b/pdf/pdfium/pdfium_on_demand_searchifier.cc
@@ -144,6 +144,7 @@
 }
 
 void PDFiumOnDemandSearchifier::SearchifyNextImage() {
+  CHECK(current_page_);
   std::optional<BitmapResult> bitmap_result = GetNextBitmap();
   if (bitmap_result.has_value()) {
     const auto& bitmap = bitmap_result.value().bitmap;
@@ -164,6 +165,11 @@
 }
 
 void PDFiumOnDemandSearchifier::CommitResultsToPage() {
+  // Ignore the results if the page got unloaded before committing them.
+  if (!current_page_) {
+    current_page_ocr_results_.clear();
+  }
+
   if (!current_page_ocr_results_.empty()) {
     // If the page is being painted, wait for paint to finish.
     if (engine_->IsPageScheduledForPaint(current_page_->index())) {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 63df65b..a7f0bfa 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -579,28 +579,27 @@
             ]
         }
     ],
-    "AndroidSpareRendererCreation_V3": [
+    "AndroidSpareRendererCreation_V2": [
         {
             "platforms": [
                 "android"
             ],
             "experiments": [
                 {
-                    "name": "CreateWithoutServiceGroupImportance",
+                    "name": "CreateByDefault_AfterLoading",
                     "params": {
                         "kill_when_backgrounded": "true",
-                        "omnibox_spare_renderer_delay_ms": "500",
-                        "spare_renderer_creation_delay_ms": "2000",
+                        "lowest-ranking": "false",
+                        "not-perceptible-binding": "true",
                         "spare_renderer_creation_timing": "after-loading",
                         "spare_renderer_timeout_seconds": "60"
                     },
                     "enable_features": [
                         "AndroidWarmUpSpareRendererWithTimeout",
-                        "OverrideAndroidOmniboxSpareRendererDelay"
+                        "SpareRendererProcessPriority"
                     ],
                     "disable_features": [
-                        "ServiceGroupImportance",
-                        "SpareRendererProcessPriority"
+                        "OverrideAndroidOmniboxSpareRendererDelay"
                     ]
                 }
             ]
@@ -2518,6 +2517,28 @@
             ]
         }
     ],
+    "AutofillSupportLastNamePrefix": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AutofillSupportLastNamePrefix",
+                        "AutofillUseNegativePatternForAllAttributes"
+                    ]
+                }
+            ]
+        }
+    ],
     "AutofillSupportPhoneticNameForJP": [
         {
             "platforms": [
@@ -5170,41 +5191,6 @@
             ]
         }
     ],
-    "ChromnientIOS": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "EnableLensOverlay",
-                        "IPH_iOSLensOverlayEntrypointTip",
-                        "LensClearcutBackgroundUploadEnabled",
-                        "LensClearcutLoggerFastQosEnabled",
-                        "LensInkMultiSampleModeDisabled",
-                        "LensOverlayEnableLocationBarEntrypoint"
-                    ],
-                    "disable_features": [
-                        "LensGestureTextSelectionDisabled",
-                        "LensOverlayEnableLocationBarEntrypointOnSRP"
-                    ]
-                },
-                {
-                    "name": "LaunchM133",
-                    "enable_features": [
-                        "EnableLensOverlay",
-                        "LensClearcutLoggerFastQosEnabled"
-                    ],
-                    "disable_features": [
-                        "LensOverlayEnableLocationBarEntrypoint",
-                        "LensOverlayEnableLocationBarEntrypointOnSRP"
-                    ]
-                }
-            ]
-        }
-    ],
     "ChromnientIPH": [
         {
             "platforms": [
@@ -11984,6 +11970,22 @@
             ]
         }
     ],
+    "IOSChromnientIPHFix": [
+        {
+            "platforms": [
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "IPH_iOSLensOverlayEntrypointTip",
+                        "LensOverlayDisableIPHPanGesture"
+                    ]
+                }
+            ]
+        }
+    ],
     "IOSCleanupHangingPasswordFormExtractionRequests": [
         {
             "platforms": [
@@ -22852,6 +22854,21 @@
             ]
         }
     ],
+    "ServiceGroupImportance": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Disabled",
+                    "disable_features": [
+                        "ServiceGroupImportance"
+                    ]
+                }
+            ]
+        }
+    ],
     "ServiceWorkerAutoPreload": [
         {
             "platforms": [
@@ -25678,6 +25695,25 @@
             ]
         }
     ],
+    "V8ManagedZoneMemory": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "V8Flag_managed_zone_memory"
+                    ]
+                }
+            ]
+        }
+    ],
     "V8ParallelReclaimUnmodifiedWrappers": [
         {
             "platforms": [
diff --git a/third_party/androidx/build.gradle b/third_party/androidx/build.gradle
index d3013508..bfa2757c 100644
--- a/third_party/androidx/build.gradle
+++ b/third_party/androidx/build.gradle
@@ -303,7 +303,7 @@
     google()
     maven {
         // This URL is generated by the fetch_all_androidx.py script.
-        url 'https://androidx.dev/snapshots/builds/13404391/artifacts/repository'
+        url 'https://androidx.dev/snapshots/builds/13405995/artifacts/repository'
     }
     mavenCentral()
 }
diff --git a/third_party/angle b/third_party/angle
index ef61ff8..34d3091 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit ef61ff8db5433d5d303df9ba6691036fcbf8bd91
+Subproject commit 34d30914048ac98a6f50f84aa630cc285b75bc79
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index 2e45f92..c4d276b 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -4851,6 +4851,8 @@
   kInputParsedAncestorSelect = 5547,
   kCSSSelectorPseudoHasSlotted = 5548,
   kSelectMultipleShowPopup = 5549,
+  kSharedWorkerExtendedLifetimeFeatureEnabled = 5550,
+  kSharedWorkerExtendedLifetimeIsTrue = 5551,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots. Also don't add extra
diff --git a/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom
index 0863cd37..a5c664c 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom
@@ -376,6 +376,8 @@
   kTextBox = 318,
   kScrollInitialTarget = 319,
   kHasSlotted = 320,
+  kElementCapture = 321,
+  kRegionCapture = 322,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/mojom/worker/shared_worker_info.mojom b/third_party/blink/public/mojom/worker/shared_worker_info.mojom
index 204d4b89..edf8fb5f 100644
--- a/third_party/blink/public/mojom/worker/shared_worker_info.mojom
+++ b/third_party/blink/public/mojom/worker/shared_worker_info.mojom
@@ -30,4 +30,9 @@
   array<network.mojom.ContentSecurityPolicy> content_security_policies;
   FetchClientSettingsObject outside_fetch_client_settings_object;
   SharedWorkerSameSiteCookies same_site_cookies = SharedWorkerSameSiteCookies.kNone;
+
+  // Indicates if SharedWorker should extend its lifetime on all clients
+  // have been destructed.
+  // See: https://github.com/whatwg/html/issues/10997
+  bool extended_lifetime;
 };
diff --git a/third_party/blink/renderer/core/css/css_unresolved_color_value.cc b/third_party/blink/renderer/core/css/css_unresolved_color_value.cc
index fda8ed0..72d3410 100644
--- a/third_party/blink/renderer/core/css/css_unresolved_color_value.cc
+++ b/third_party/blink/renderer/core/css/css_unresolved_color_value.cc
@@ -27,8 +27,8 @@
                           Color::ColorSpace color_space,
                           StringBuilder& result) {
   if (value) {
-    if (value->IsNumericLiteralValue()) {
-      double val = value->GetDoubleValue();
+    if (const auto* literal = DynamicTo<CSSNumericLiteralValue>(value)) {
+      double val = literal->GetDoubleValue();
       if (value->IsPercentage()) {
         val /= 100.0;
       }
@@ -83,8 +83,9 @@
     // known to be 1.0 or above. (Note: alpha_->IsOne() would return false for
     // e.g. 1.5.).
     std::optional<double> alpha;
-    if (alpha_->IsNumericLiteralValue()) {
-      alpha = alpha_->GetDoubleValue();
+    if (const auto* literal_alpha =
+            DynamicTo<CSSNumericLiteralValue>(*alpha_)) {
+      alpha = literal_alpha->GetDoubleValue();
     } else {
       // See corresponding code in AppendChannel().
       const CSSMathFunctionValue* calc =
diff --git a/third_party/blink/renderer/core/css/parser/find_length_of_declaration_list-inl.h b/third_party/blink/renderer/core/css/parser/find_length_of_declaration_list-inl.h
index b6253440..cee0ed5 100644
--- a/third_party/blink/renderer/core/css/parser/find_length_of_declaration_list-inl.h
+++ b/third_party/blink/renderer/core/css/parser/find_length_of_declaration_list-inl.h
@@ -151,7 +151,11 @@
     // Unescaped newlines within quotes are not allowed; they terminate
     // the string. We don't want to complicate our handling beyond
     // detecting it, so we treat it as an error and abort.
-    const __m128i eq_newline = _mm_cmpeq_epi8(x, _mm_set1_epi8('\n'));
+    // \r, \n and \f all count as newlines in this regard
+    // (see IsCSSNewLine()).
+    const __m128i eq_newline = _mm_cmpeq_epi8(x, _mm_set1_epi8('\n')) |
+                               _mm_cmpeq_epi8(x, _mm_set1_epi8('\r')) |
+                               _mm_cmpeq_epi8(x, _mm_set1_epi8('\f'));
     const __m128i quoted_newline = is_quoted & eq_newline;
 
     // Now we have a mask of bytes that are inside quotes
@@ -387,7 +391,18 @@
     // parens within quotes.
     __m256i quoted_mask = MaskToAVX2(prefix_single_quote | prefix_double_quote);
 
-    const __m256i eq_newline = _mm256_cmpeq_epi8(x, _mm256_set1_epi8('\n'));
+    // Since we have PSHUFB, and the values we're looking for (0x0a, 0x0c, 0x0d)
+    // all share a nibble (the upper nibble is always zero), we can use a
+    // shuffle plus compare instead of three compares and ORs. Basically this is
+    // the test `x == table[x & 0x0f]` (modulo some PSHUFB behavior with the top
+    // bit of x, that we don't need to worry about here), where we engineer
+    // table[] so that this only holds true for the three values of x we care
+    // about.
+    const __m256i eq_newline_table =
+        _mm256_setr_epi64x(0x0000000000000001, 0x00000d0c000a0000,
+                           0x0000000000000000, 0x0000000000000000);
+    const __m256i eq_newline =
+        _mm256_cmpeq_epi8(x, _mm256_shuffle_epi8(eq_newline_table, x));
     const __m256i quoted_newline = quoted_mask & eq_newline;
 
     const __m256i comment_start =
@@ -527,7 +542,18 @@
     const uint8x16_t mixed_quote = quoted == static_cast<char>('\'' ^ '"');
 
     const uint8x16_t is_quoted = quoted > vdupq_n_u8(0);
-    const uint8x16_t eq_newline = x == '\n';
+    // The VTBL instruction returns zero for indexes outside [0,16), so we
+    // don't need to be clever at all here, unlike with AVX2.
+#ifdef ARCH_CPU_ARM64
+    const uint8x16_t eq_newline = vqtbl1q_u8(
+        uint8x16_t{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0, 0xff, 0xff, 0, 0}, x);
+#else
+    const uint8x8x2_t eq_newline_table{0, 0, 0,    0, 0,    0,    0, 0,
+                                       0, 0, 0xff, 0, 0xff, 0xff, 0, 0};
+    const uint8x16_t eq_newline =
+        vcombine_s8(vtbl2_u8(eq_newline_table, vget_low_s8(x)),
+                    vtbl2_u8(eq_newline_table, vget_high_s8(x)));
+#endif
     const uint8x16_t quoted_newline = is_quoted & eq_newline;
 
     x &= ~is_quoted;
diff --git a/third_party/blink/renderer/core/css/parser/find_length_of_declaration_list_test.cc b/third_party/blink/renderer/core/css/parser/find_length_of_declaration_list_test.cc
index e4ebbb0f..fc62a78 100644
--- a/third_party/blink/renderer/core/css/parser/find_length_of_declaration_list_test.cc
+++ b/third_party/blink/renderer/core/css/parser/find_length_of_declaration_list_test.cc
@@ -178,6 +178,10 @@
   // which immediately ends the string), so we give up here.
   EXPECT_FALSE(BlockAccepted(String::FromUTF8("--foo: \"\n\"")));
   EXPECT_FALSE(BlockAccepted(String::FromUTF8("--foo: '\n'")));
+  EXPECT_FALSE(BlockAccepted(String::FromUTF8("--foo: \"\r\"")));
+  EXPECT_FALSE(BlockAccepted(String::FromUTF8("--foo: '\r'")));
+  EXPECT_FALSE(BlockAccepted(String::FromUTF8("--foo: \"\f\"")));
+  EXPECT_FALSE(BlockAccepted(String::FromUTF8("--foo: '\f'")));
 
   // However, newlines in general are allowed.
   EXPECT_TRUE(BlockAccepted(String::FromUTF8("--foo: 'bar'\n--bar: 'foo'\n")));
diff --git a/third_party/blink/renderer/core/html/parser/html_parser_idioms.cc b/third_party/blink/renderer/core/html/parser/html_parser_idioms.cc
index 263e123..93d701a 100644
--- a/third_party/blink/renderer/core/html/parser/html_parser_idioms.cc
+++ b/third_party/blink/renderer/core/html/parser/html_parser_idioms.cc
@@ -183,27 +183,23 @@
     return false;
 
   return WTF::VisitCharacters(input, [&](auto chars) {
-    const auto* position = chars.data();
-    using CharacterType = std::decay_t<decltype(*position)>;
-    const auto* end = position + chars.size();
+    using CharacterType = std::decay_t<decltype(chars[0])>;
 
     // Step 4
-    SkipWhile<CharacterType, IsHTMLSpace<CharacterType>>(position, end);
+    size_t position =
+        SkipWhile<CharacterType, IsHTMLSpace<CharacterType>>(chars, 0);
 
     // Step 5
-    if (position == end) {
+    if (position == chars.size()) {
       return false;
     }
-    DCHECK_LT(position, end);
+    DCHECK_LT(position, chars.size());
 
     bool ok;
     constexpr auto kOptions = WTF::NumberParsingOptions()
                                   .SetAcceptTrailingGarbage()
                                   .SetAcceptLeadingPlus();
-    int wtf_value =
-        CharactersToInt(base::span<const CharacterType>(
-                            position, static_cast<size_t>(end - position)),
-                        kOptions, &ok);
+    int wtf_value = CharactersToInt(chars.subspan(position), kOptions, &ok);
     if (ok) {
       value = wtf_value;
     }
@@ -220,9 +216,7 @@
 
   return WTF::VisitCharacters(
       input, [&](auto chars) {
-        const auto* position = chars.data();
-        using CharacterType = std::decay_t<decltype(*position)>;
-        const auto* end = position + chars.size();
+        using CharacterType = std::decay_t<decltype(chars[0])>;
 
         // This function is an implementation of the following algorithm:
         // https://html.spec.whatwg.org/C/#rules-for-parsing-non-negative-integers
@@ -233,20 +227,22 @@
         // https://html.spec.whatwg.org/C/#rules-for-parsing-integers
 
         // Step 4: Skip whitespace.
-        SkipWhile<CharacterType, IsHTMLSpace<CharacterType>>(position, end);
+        size_t position =
+            SkipWhile<CharacterType, IsHTMLSpace<CharacterType>>(chars, 0);
 
         // Step 5: If position is past the end of input, return an error.
-        if (position == end)
+        if (position == chars.size()) {
           return WTF::NumberParsingResult::kError;
-        DCHECK_LT(position, end);
+        }
+        DCHECK_LT(position, chars.size());
 
         WTF::NumberParsingResult result;
         constexpr auto kOptions = WTF::NumberParsingOptions()
                                       .SetAcceptTrailingGarbage()
                                       .SetAcceptLeadingPlus()
                                       .SetAcceptMinusZeroForUnsigned();
-        unsigned wtf_value = CharactersToUInt(
-            {position, static_cast<size_t>(end - position)}, kOptions, &result);
+        unsigned wtf_value =
+            CharactersToUInt(chars.subspan(position), kOptions, &result);
         if (result == WTF::NumberParsingResult::kSuccess)
           value = wtf_value;
         return result;
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
index cd201c8..028ffd1 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -294,13 +294,9 @@
 
 namespace {
 
-void SetNodeInfo(perfetto::TracedDictionary& dict,
-                 Node* node,
-                 perfetto::StaticString id_field_name,
-                 perfetto::StaticString name_field_name = nullptr) {
-  dict.Add(id_field_name, IdentifiersFactory::IntIdForNode(node));
-  if (name_field_name.value)
-    dict.Add(name_field_name, node->DebugName());
+void SetNodeInfo(perfetto::TracedDictionary& dict, Node* node) {
+  dict.Add("nodeId", IdentifiersFactory::IntIdForNode(node));
+  dict.Add("nodeName", node->DebugName());
 }
 
 const char* PseudoTypeToString(CSSSelector::PseudoType pseudo_type) {
@@ -538,7 +534,7 @@
                     const InvalidationSet& invalidation_set,
                     const char* invalidated_selector) {
   dict.Add("frame", IdentifiersFactory::FrameId(node.GetDocument().GetFrame()));
-  SetNodeInfo(dict, &node, "nodeId", "nodeName");
+  SetNodeInfo(dict, &node);
   dict.Add("invalidationSet",
            DescendantInvalidationSetToIdString(invalidation_set));
   dict.Add("invalidatedSelectorId", invalidated_selector);
@@ -662,7 +658,7 @@
                     ContainerNode& node,
                     const char* reason) {
   dict.Add("frame", IdentifiersFactory::FrameId(node.GetDocument().GetFrame()));
-  SetNodeInfo(dict, &node, "nodeId", "nodeName");
+  SetNodeInfo(dict, &node);
   dict.Add("reason", reason);
 }
 void FillSelectors(
@@ -754,7 +750,7 @@
   auto dict = std::move(context).WriteDictionary();
   dict.Add("frame",
            IdentifiersFactory::FrameId(node->GetDocument().GetFrame()));
-  SetNodeInfo(dict, node, "nodeId", "nodeName");
+  SetNodeInfo(dict, node);
   dict.Add("subtree", change_type == kSubtreeStyleChange);
   dict.Add("reason", reason.ReasonString());
   dict.Add("extraData", reason.GetExtraData());
@@ -804,24 +800,21 @@
   array.Append(quad.p4().y());
 }
 
-static void SetGeneratingNodeInfo(
-    perfetto::TracedDictionary& dict,
-    const LayoutObject* layout_object,
-    perfetto::StaticString id_field_name,
-    perfetto::StaticString name_field_name = nullptr) {
+static void SetGeneratingNodeInfo(perfetto::TracedDictionary& dict,
+                                  const LayoutObject* layout_object) {
   Node* node = nullptr;
   for (; layout_object && !node; layout_object = layout_object->Parent())
     node = layout_object->GeneratingNode();
   if (!node)
     return;
 
-  SetNodeInfo(dict, node, id_field_name, name_field_name);
+  SetNodeInfo(dict, node);
 }
 
 static void CreateLayoutRoot(perfetto::TracedValue context,
                              const LayoutObjectWithDepth& layout_root) {
   auto dict = std::move(context).WriteDictionary();
-  SetGeneratingNodeInfo(dict, layout_root.object, "nodeId");
+  SetGeneratingNodeInfo(dict, layout_root.object);
   dict.Add("depth", static_cast<int>(layout_root.depth));
   Vector<gfx::QuadF> quads;
   layout_root.object->AbsoluteQuads(quads);
@@ -909,7 +902,7 @@
   DCHECK(layout_object);
   auto dict = std::move(context).WriteDictionary();
   dict.Add("frame", IdentifiersFactory::FrameId(layout_object->GetFrame()));
-  SetGeneratingNodeInfo(dict, layout_object, "nodeId", "nodeName");
+  SetGeneratingNodeInfo(dict, layout_object);
   dict.Add("reason", reason);
   auto source_location = CaptureSourceLocation();
   if (source_location->HasStackTrace())
@@ -1301,7 +1294,7 @@
   auto dict = std::move(context).WriteDictionary();
   dict.Add("frame", IdentifiersFactory::FrameId(frame));
   CreateQuad(dict.AddItem("clip"), gfx::QuadF(gfx::RectF(contents_cull_rect)));
-  SetGeneratingNodeInfo(dict, layout_object, "nodeId");
+  SetGeneratingNodeInfo(dict, layout_object);
   dict.Add("layerId", 0);  // For backward compatibility.
   SetCallStack(frame->DomWindow()->GetIsolate(), dict);
 }
@@ -1362,7 +1355,7 @@
                                         LayoutObject* layout_object) {
   auto dict = std::move(context).WriteDictionary();
   dict.Add("frame", IdentifiersFactory::FrameId(layout_object->GetFrame()));
-  SetGeneratingNodeInfo(dict, layout_object, "nodeId");
+  SetGeneratingNodeInfo(dict, layout_object);
 }
 
 namespace {
@@ -1544,7 +1537,7 @@
                                        const gfx::RectF& src_rect,
                                        const gfx::RectF& dest_rect) {
   auto dict = std::move(context).WriteDictionary();
-  SetGeneratingNodeInfo(dict, &layout_image, "nodeId", "nodeName");
+  SetGeneratingNodeInfo(dict, &layout_image);
   if (const ImageResourceContent* content = layout_image.CachedImage())
     dict.Add("url", content->Url().ElidedString());
 
@@ -1574,7 +1567,7 @@
                                        const LayoutObject& owning_layout_object,
                                        const StyleImage& style_image) {
   auto dict = std::move(context).WriteDictionary();
-  SetGeneratingNodeInfo(dict, &owning_layout_object, "nodeId");
+  SetGeneratingNodeInfo(dict, &owning_layout_object);
   if (const ImageResourceContent* content = style_image.CachedImage())
     dict.Add("url", content->Url().ElidedString());
 }
@@ -1586,7 +1579,7 @@
                                        const gfx::RectF& dest_rect) {
   auto dict = std::move(context).WriteDictionary();
   if (node) {
-    SetNodeInfo(dict, node, "nodeId", nullptr);
+    SetNodeInfo(dict, node);
     if (LocalFrame* frame = node->GetDocument().GetFrame()) {
       dict.Add("frame", IdentifiersFactory::FrameId(frame));
     }
@@ -1608,7 +1601,7 @@
     const LayoutObject* owning_layout_object,
     const ImageResourceContent& image_content) {
   auto dict = std::move(context).WriteDictionary();
-  SetGeneratingNodeInfo(dict, owning_layout_object, "nodeId");
+  SetGeneratingNodeInfo(dict, owning_layout_object);
   dict.Add("url", image_content.Url().ElidedString());
 }
 
@@ -1833,15 +1826,13 @@
   if (dom_stats->max_children_node) {
     auto max_children_dict = dict.AddDictionary("maxChildren");
     max_children_dict.Add("numChildren", dom_stats->max_children);
-    SetNodeInfo(max_children_dict, dom_stats->max_children_node, "nodeId",
-                "nodeName");
+    SetNodeInfo(max_children_dict, dom_stats->max_children_node);
   }
 
   if (dom_stats->max_depth_node) {
     auto max_depth_dict = dict.AddDictionary("maxDepth");
     max_depth_dict.Add("depth", dom_stats->max_depth);
-    SetNodeInfo(max_depth_dict, dom_stats->max_depth_node, "nodeId",
-                "nodeName");
+    SetNodeInfo(max_depth_dict, dom_stats->max_depth_node);
   }
 }
 
@@ -1858,7 +1849,7 @@
     dict.Add("name", animation.id());
     if (auto* frame_effect = DynamicTo<KeyframeEffect>(effect)) {
       if (Element* target = frame_effect->EffectTarget())
-        SetNodeInfo(dict, target, "nodeId", "nodeName");
+        SetNodeInfo(dict, target);
     }
   }
 }
@@ -1904,7 +1895,7 @@
   if (request.ListBased())
     dict.Add("listBased", true);
   else if (Node* node = result.InnerNode())
-    SetNodeInfo(dict, node, "nodeId", "nodeName");
+    SetNodeInfo(dict, node);
 }
 
 void inspector_async_task::Data(perfetto::TracedValue context,
diff --git a/third_party/blink/renderer/core/layout/block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/block_layout_algorithm.cc
index c96b568..e7af0c4 100644
--- a/third_party/blink/renderer/core/layout/block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/block_layout_algorithm.cc
@@ -389,7 +389,8 @@
   column_spanner_path_ = previous.column_spanner_path_;
 
   if (relayout_type == kRelayoutIgnoringLineClamp) {
-    line_clamp_data_.data.state = LineClampData::kDontTruncate;
+    line_clamp_data_.data.state = LineClampData::kDisabled;
+    line_clamp_data_.ignore_line_clamp = true;
   } else if (relayout_type == kRelayoutWithLineClampBlockSize) {
     line_clamp_data_.data.state = LineClampData::kClampByLines;
     line_clamp_data_.data.lines_until_clamp =
@@ -401,9 +402,6 @@
     line_clamp_data_.data.lines_until_clamp =
         line_clamp_data_.initial_lines_until_clamp =
             previous.line_clamp_data_.initial_lines_until_clamp;
-  } else if (previous.line_clamp_data_.data.state ==
-             LineClampData::kDontTruncate) {
-    line_clamp_data_.data.state = LineClampData::kDontTruncate;
   }
 
   if (relayout_type == kRelayoutForTextBoxTrim) {
diff --git a/third_party/blink/renderer/core/layout/block_layout_algorithm.h b/third_party/blink/renderer/core/layout/block_layout_algorithm.h
index ef87d9e..86c1313 100644
--- a/third_party/blink/renderer/core/layout/block_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/block_layout_algorithm.h
@@ -88,7 +88,8 @@
 
   void UpdateClampOffsetFromStyle(LayoutUnit clamp_bfc_offset,
                                   LayoutUnit content_edge) {
-    if (data.state == LineClampData::kDontTruncate) {
+    if (ignore_line_clamp) {
+      DCHECK_EQ(data.state, LineClampData::kDisabled);
       return;
     }
 
@@ -102,7 +103,7 @@
 
     DCHECK_EQ(data.state, LineClampData::kDisabled);
     if (clamp_bfc_offset == kIndefiniteSize) {
-      data.state = LineClampData::kDontTruncate;
+      data.state = LineClampData::kDisabled;
     } else {
       data.state = LineClampData::kMeasureLinesUntilBfcOffset;
       data.lines_until_clamp = 0;
@@ -111,7 +112,8 @@
   }
 
   void UpdateLinesFromStyle(int lines_until_clamp) {
-    if (data.state == LineClampData::kDontTruncate) {
+    if (ignore_line_clamp) {
+      DCHECK_EQ(data.state, LineClampData::kDisabled);
       return;
     }
 
@@ -185,6 +187,9 @@
 
   LineClampData data;
 
+  // Set to true when we relayout ignoring line clamp.
+  bool ignore_line_clamp = false;
+
   // TODO(abotella): Make the following fields into a union.
 
   // The initial number of lines until clamp from the start of this layout.
diff --git a/third_party/blink/renderer/core/layout/inline/inline_layout_algorithm.cc b/third_party/blink/renderer/core/layout/inline/inline_layout_algorithm.cc
index 60d7014..3434e305 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_layout_algorithm.cc
@@ -327,18 +327,17 @@
                                          LayoutUnit line_box_height) const {
   const ConstraintSpace& space = GetConstraintSpace();
   LineClampData line_clamp_data = space.GetLineClampData();
-  if (line_clamp_data.IsLineClampContext()) {
-    if (!line_info->IsBlockInInline() && line_clamp_data.IsAtClampPoint()) {
-      if (RuntimeEnabledFeatures::CSSLineClampLineBreakingEllipsisEnabled()) {
-        return LineClampState::kLineClampEllipsis;
-      }
-      return LineClampState::kTextOverflowEllipsis;
+  if (!line_info->IsBlockInInline() && line_clamp_data.IsAtClampPoint()) {
+    if (RuntimeEnabledFeatures::CSSLineClampLineBreakingEllipsisEnabled()) {
+      return LineClampState::kLineClampEllipsis;
     }
-    if (line_clamp_data.ShouldHideForPaint()) {
-      return LineClampState::kHide;
-    }
-  } else if (!line_info->IsBlockInInline() && line_info->HasOverflow() &&
-             node_.GetLayoutBlockFlow()->ShouldTruncateOverflowingText()) {
+    return LineClampState::kTextOverflowEllipsis;
+  }
+  if (line_clamp_data.ShouldHideForPaint()) {
+    return LineClampState::kHide;
+  }
+  if (!line_info->IsBlockInInline() && line_info->HasOverflow() &&
+      node_.GetLayoutBlockFlow()->ShouldTruncateOverflowingText()) {
     return LineClampState::kTextOverflowEllipsis;
   }
 
diff --git a/third_party/blink/renderer/core/layout/line_clamp_data.h b/third_party/blink/renderer/core/layout/line_clamp_data.h
index df4a9ffc..a25388b6 100644
--- a/third_party/blink/renderer/core/layout/line_clamp_data.h
+++ b/third_party/blink/renderer/core/layout/line_clamp_data.h
@@ -21,10 +21,6 @@
     kDisabled,
     kClampByLines,
     kMeasureLinesUntilBfcOffset,
-    // The line-clamp context is enabled, but no forced truncation
-    // will happen. This is different from kDisabled in that
-    // `text-overflow: ellipsis` will not take effect inside it.
-    kDontTruncate,
   };
 
   bool IsLineClampContext() const { return state != kDisabled; }
diff --git a/third_party/blink/renderer/core/svg/svg_view_spec.cc b/third_party/blink/renderer/core/svg/svg_view_spec.cc
index ad725a21..5ec396a 100644
--- a/third_party/blink/renderer/core/svg/svg_view_spec.cc
+++ b/third_party/blink/renderer/core/svg/svg_view_spec.cc
@@ -72,9 +72,8 @@
 bool SVGViewSpec::ParseViewSpec(const String& spec) {
   if (spec.empty())
     return false;
-  return WTF::VisitCharacters(spec, [&](auto chars) {
-    return ParseViewSpecInternal(chars.data(), chars.data() + chars.size());
-  });
+  return WTF::VisitCharacters(
+      spec, [&](auto chars) { return ParseViewSpecInternal(chars); });
 }
 
 namespace {
@@ -118,21 +117,28 @@
 }  // namespace
 
 template <typename CharType>
-bool SVGViewSpec::ParseViewSpecInternal(const CharType* ptr,
-                                        const CharType* end) {
+bool SVGViewSpec::ParseViewSpecInternal(base::span<const CharType> chars) {
+  const CharType* ptr = chars.data();
+  const CharType* end = UNSAFE_TODO(ptr + chars.size());
   if (!SkipToken(ptr, end, "svgView"))
     return false;
 
-  if (!SkipExactly<CharType>(ptr, end, '('))
+  size_t position = ptr - chars.data();
+  if (!SkipExactly<CharType>(chars, '(', position)) {
     return false;
+  }
+  ptr = UNSAFE_TODO(chars.data() + position);
 
   while (ptr < end && *ptr != ')') {
     ViewSpecFunctionType function_type = ScanViewSpecFunction(ptr, end);
     if (function_type == kUnknown)
       return false;
 
-    if (!SkipExactly<CharType>(ptr, end, '('))
+    position = ptr - chars.data();
+    if (!SkipExactly<CharType>(chars, '(', position)) {
       return false;
+    }
+    ptr = UNSAFE_TODO(chars.data() + position);
 
     switch (function_type) {
       case kViewBox: {
@@ -178,12 +184,15 @@
         NOTREACHED();
     }
 
-    if (!SkipExactly<CharType>(ptr, end, ')'))
+    position = ptr - chars.data();
+    if (!SkipExactly<CharType>(chars, ')', position)) {
       return false;
+    }
 
-    SkipExactly<CharType>(ptr, end, ';');
+    SkipExactly<CharType>(chars, ';', position);
+    ptr = UNSAFE_TODO(chars.data() + position);
   }
-  return SkipExactly<CharType>(ptr, end, ')');
+  return SkipExactly<CharType>(chars, ')', position);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/svg/svg_view_spec.h b/third_party/blink/renderer/core/svg/svg_view_spec.h
index 798684f..d9c3db7 100644
--- a/third_party/blink/renderer/core/svg/svg_view_spec.h
+++ b/third_party/blink/renderer/core/svg/svg_view_spec.h
@@ -50,7 +50,7 @@
  private:
   bool ParseViewSpec(const String&);
   template <typename CharType>
-  bool ParseViewSpecInternal(const CharType* ptr, const CharType* end);
+  bool ParseViewSpecInternal(base::span<const CharType> chars);
 
   Member<const SVGRect> view_box_;
   Member<const SVGPreserveAspectRatio> preserve_aspect_ratio_;
diff --git a/third_party/blink/renderer/core/workers/shared_worker.cc b/third_party/blink/renderer/core/workers/shared_worker.cc
index 11be302..daac3f1 100644
--- a/third_party/blink/renderer/core/workers/shared_worker.cc
+++ b/third_party/blink/renderer/core/workers/shared_worker.cc
@@ -161,6 +161,7 @@
       window->GetStorageKey().IsFirstPartyContext()
           ? mojom::blink::SharedWorkerSameSiteCookies::kAll
           : mojom::blink::SharedWorkerSameSiteCookies::kNone;
+  bool extended_lifetime = false;
   switch (name_or_options->GetContentType()) {
     case V8UnionSharedWorkerOptionsOrString::ContentType::kString:
       options->name = name_or_options->GetAsString();
@@ -201,6 +202,15 @@
             break;
         }
       }
+      if (worker_options->hasExtendedLifetime()) {
+        extended_lifetime = worker_options->extendedLifetime();
+        UseCounter::Count(
+            window, WebFeature::kSharedWorkerExtendedLifetimeFeatureEnabled);
+        if (extended_lifetime) {
+          UseCounter::Count(window,
+                            WebFeature::kSharedWorkerExtendedLifetimeIsTrue);
+        }
+      }
       break;
     }
   }
@@ -213,7 +223,7 @@
   SharedWorkerClientHolder::From(*window)->Connect(
       worker, std::move(remote_port), script_url, std::move(blob_url_token),
       std::move(options), same_site_cookies, context->UkmSourceID(),
-      connector_override);
+      connector_override, extended_lifetime);
 
   return worker;
 }
diff --git a/third_party/blink/renderer/core/workers/shared_worker.idl b/third_party/blink/renderer/core/workers/shared_worker.idl
index 0d32f11..4263a6fd 100644
--- a/third_party/blink/renderer/core/workers/shared_worker.idl
+++ b/third_party/blink/renderer/core/workers/shared_worker.idl
@@ -33,6 +33,9 @@
 // https://privacycg.github.io/saa-non-cookie-storage/#shared-worker
 dictionary SharedWorkerOptions : WorkerOptions {
     SharedWorkerSameSiteCookies sameSiteCookies;
+    // Experimental.
+    // See: https://github.com/whatwg/html/issues/10997
+    [RuntimeEnabled=SharedWorkerExtendedLifetime] boolean extendedLifetime;
 };
 
 enum SharedWorkerSameSiteCookies { "all", "none" };
diff --git a/third_party/blink/renderer/core/workers/shared_worker_client_holder.cc b/third_party/blink/renderer/core/workers/shared_worker_client_holder.cc
index 536acd6..5b3b99e2 100644
--- a/third_party/blink/renderer/core/workers/shared_worker_client_holder.cc
+++ b/third_party/blink/renderer/core/workers/shared_worker_client_holder.cc
@@ -92,7 +92,8 @@
     mojom::blink::SharedWorkerSameSiteCookies same_site_cookies,
     ukm::SourceId client_ukm_source_id,
     const HeapMojoRemote<mojom::blink::SharedWorkerConnector>*
-        connector_override) {
+        connector_override,
+    bool extended_lifetime) {
   DCHECK(IsMainThread());
   DCHECK(options);
 
@@ -123,7 +124,7 @@
           outside_fetch_client_settings_object->GetReferrerPolicy(),
           KURL(outside_fetch_client_settings_object->GetOutgoingReferrer()),
           insecure_requests_policy),
-      same_site_cookies);
+      same_site_cookies, extended_lifetime);
 
   const HeapMojoRemote<mojom::blink::SharedWorkerConnector>& connector =
       connector_override ? *connector_override : connector_;
diff --git a/third_party/blink/renderer/core/workers/shared_worker_client_holder.h b/third_party/blink/renderer/core/workers/shared_worker_client_holder.h
index f8aa10f..b5ca6b42 100644
--- a/third_party/blink/renderer/core/workers/shared_worker_client_holder.h
+++ b/third_party/blink/renderer/core/workers/shared_worker_client_holder.h
@@ -83,7 +83,8 @@
                mojom::blink::SharedWorkerSameSiteCookies same_site_cookies,
                ukm::SourceId client_ukm_source_id,
                const HeapMojoRemote<mojom::blink::SharedWorkerConnector>*
-                   connector_override);
+                   connector_override,
+               bool extended_lifetime);
 
   void Trace(Visitor* visitor) const override;
 
diff --git a/third_party/blink/renderer/modules/webgl/BUILD.gn b/third_party/blink/renderer/modules/webgl/BUILD.gn
index 29cc583..54d8f85 100644
--- a/third_party/blink/renderer/modules/webgl/BUILD.gn
+++ b/third_party/blink/renderer/modules/webgl/BUILD.gn
@@ -104,10 +104,6 @@
     "webgl_context_attribute_helpers.h",
     "webgl_context_event.cc",
     "webgl_context_event.h",
-    "webgl_context_group.cc",
-    "webgl_context_group.h",
-    "webgl_context_object.cc",
-    "webgl_context_object.h",
     "webgl_debug_renderer_info.cc",
     "webgl_debug_renderer_info.h",
     "webgl_debug_shaders.cc",
@@ -159,10 +155,6 @@
     "webgl_shader_pixel_local_storage.h",
     "webgl_shader_precision_format.cc",
     "webgl_shader_precision_format.h",
-    "webgl_shared_object.cc",
-    "webgl_shared_object.h",
-    "webgl_shared_platform_3d_object.cc",
-    "webgl_shared_platform_3d_object.h",
     "webgl_stencil_texturing.cc",
     "webgl_stencil_texturing.h",
     "webgl_sync.cc",
diff --git a/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query.cc b/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query.cc
index 79805cf1..ac20a1a0 100644
--- a/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query.cc
+++ b/third_party/blink/renderer/modules/webgl/ext_disjoint_timer_query.cc
@@ -38,7 +38,7 @@
   if (!query || scoped.IsLost())
     return;
 
-  if (!query->Validate(nullptr, scoped.Context())) {
+  if (!query->Validate(scoped.Context())) {
     scoped.Context()->SynthesizeGLError(
         GL_INVALID_OPERATION, "delete",
         "object does not belong to this context");
@@ -61,7 +61,7 @@
 bool EXTDisjointTimerQuery::isQueryEXT(WebGLTimerQueryEXT* query) {
   WebGLExtensionScopedContext scoped(this);
   if (!query || scoped.IsLost() || query->MarkedForDeletion() ||
-      !query->Validate(nullptr, scoped.Context())) {
+      !query->Validate(scoped.Context())) {
     return false;
   }
 
diff --git a/third_party/blink/renderer/modules/webgl/oes_vertex_array_object.cc b/third_party/blink/renderer/modules/webgl/oes_vertex_array_object.cc
index ff038d2..82cec872 100644
--- a/third_party/blink/renderer/modules/webgl/oes_vertex_array_object.cc
+++ b/third_party/blink/renderer/modules/webgl/oes_vertex_array_object.cc
@@ -65,8 +65,7 @@
 
   // ValidateWebGLObject generates an error if the object has already been
   // deleted, so we must replicate most of its checks here.
-  if (!array_object->Validate(scoped.Context()->ContextGroup(),
-                              scoped.Context())) {
+  if (!array_object->Validate(scoped.Context())) {
     scoped.Context()->SynthesizeGLError(
         GL_INVALID_OPERATION, "deleteVertexArrayOES",
         "object does not belong to this context");
@@ -87,9 +86,9 @@
     WebGLVertexArrayObjectOES* array_object) {
   WebGLExtensionScopedContext scoped(this);
   if (scoped.IsLost() || !array_object ||
-      !array_object->Validate(scoped.Context()->ContextGroup(),
-                              scoped.Context()))
+      !array_object->Validate(scoped.Context())) {
     return false;
+  }
 
   if (!array_object->HasEverBeenBound())
     return false;
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
index 7d7335b..8895174 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
@@ -3539,8 +3539,9 @@
 }
 
 bool WebGL2RenderingContextBase::isQuery(WebGLQuery* query) {
-  if (!query || isContextLost() || !query->Validate(ContextGroup(), this))
+  if (!query || isContextLost() || !query->Validate(this)) {
     return false;
+  }
 
   if (query->MarkedForDeletion())
     return false;
@@ -3757,8 +3758,9 @@
 }
 
 bool WebGL2RenderingContextBase::isSampler(WebGLSampler* sampler) {
-  if (!sampler || isContextLost() || !sampler->Validate(ContextGroup(), this))
+  if (!sampler || isContextLost() || !sampler->Validate(this)) {
     return false;
+  }
 
   if (sampler->MarkedForDeletion())
     return false;
@@ -3965,8 +3967,9 @@
 }
 
 bool WebGL2RenderingContextBase::isSync(WebGLSync* sync) {
-  if (!sync || isContextLost() || !sync->Validate(ContextGroup(), this))
+  if (!sync || isContextLost() || !sync->Validate(this)) {
     return false;
+  }
 
   if (sync->MarkedForDeletion())
     return false;
@@ -4062,8 +4065,7 @@
     WebGLTransformFeedback* feedback) {
   // We have to short-circuit the deletion process if the transform feedback is
   // active. This requires duplication of some validation logic.
-  if (!isContextLost() && feedback &&
-      feedback->Validate(ContextGroup(), this)) {
+  if (!isContextLost() && feedback && feedback->Validate(this)) {
     if (feedback->active()) {
       SynthesizeGLError(
           GL_INVALID_OPERATION, "deleteTransformFeedback",
@@ -4081,8 +4083,9 @@
 
 bool WebGL2RenderingContextBase::isTransformFeedback(
     WebGLTransformFeedback* feedback) {
-  if (!feedback || isContextLost() || !feedback->Validate(ContextGroup(), this))
+  if (!feedback || isContextLost() || !feedback->Validate(this)) {
     return false;
+  }
 
   if (!feedback->HasEverBeenBound())
     return false;
@@ -4692,7 +4695,7 @@
   // deleted, so we must replicate most of its checks here.
   if (isContextLost() || !vertex_array)
     return;
-  if (!vertex_array->Validate(ContextGroup(), this)) {
+  if (!vertex_array->Validate(this)) {
     SynthesizeGLError(GL_INVALID_OPERATION, "deleteVertexArray",
                       "object does not belong to this context");
     return;
@@ -4709,9 +4712,9 @@
 
 bool WebGL2RenderingContextBase::isVertexArray(
     WebGLVertexArrayObject* vertex_array) {
-  if (isContextLost() || !vertex_array ||
-      !vertex_array->Validate(ContextGroup(), this))
+  if (isContextLost() || !vertex_array || !vertex_array->Validate(this)) {
     return false;
+  }
 
   if (!vertex_array->HasEverBeenBound())
     return false;
@@ -5469,11 +5472,11 @@
     }
   }
 
-  WebGLSharedObject* attachment_object = nullptr;
+  WebGLObject* attachment_object = nullptr;
   if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) {
-    WebGLSharedObject* depth_attachment =
+    WebGLObject* depth_attachment =
         framebuffer_binding->GetAttachmentObject(GL_DEPTH_ATTACHMENT);
-    WebGLSharedObject* stencil_attachment =
+    WebGLObject* stencil_attachment =
         framebuffer_binding->GetAttachmentObject(GL_STENCIL_ATTACHMENT);
     if (depth_attachment != stencil_attachment) {
       SynthesizeGLError(
diff --git a/third_party/blink/renderer/modules/webgl/webgl_buffer.cc b/third_party/blink/renderer/modules/webgl/webgl_buffer.cc
index e6f0ffc1..a567d13 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_buffer.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_buffer.cc
@@ -31,7 +31,7 @@
 namespace blink {
 
 WebGLBuffer::WebGLBuffer(WebGLRenderingContextBase* ctx)
-    : WebGLSharedPlatform3DObject(ctx), initial_target_(0), size_(0) {
+    : WebGLObject(ctx), initial_target_(0), size_(0) {
   if (!ctx->isContextLost()) {
     GLuint buffer;
     ctx->ContextGL()->GenBuffers(1, &buffer);
@@ -42,8 +42,7 @@
 WebGLBuffer::~WebGLBuffer() = default;
 
 void WebGLBuffer::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
-  gl->DeleteBuffers(1, &object_);
-  object_ = 0;
+  gl->DeleteBuffers(1, &Object());
 }
 
 void WebGLBuffer::SetInitialTarget(GLenum target) {
diff --git a/third_party/blink/renderer/modules/webgl/webgl_buffer.h b/third_party/blink/renderer/modules/webgl/webgl_buffer.h
index b524381..2f195497 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_buffer.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_buffer.h
@@ -26,11 +26,11 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_BUFFER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_BUFFER_H_
 
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 
 namespace blink {
 
-class WebGLBuffer final : public WebGLSharedPlatform3DObject {
+class WebGLBuffer final : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -49,8 +49,6 @@
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
  private:
-  bool IsBuffer() const override { return true; }
-
   GLenum initial_target_;
   int64_t size_;
 };
diff --git a/third_party/blink/renderer/modules/webgl/webgl_context_group.cc b/third_party/blink/renderer/modules/webgl/webgl_context_group.cc
deleted file mode 100644
index a054373..0000000
--- a/third_party/blink/renderer/modules/webgl/webgl_context_group.cc
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2011 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "third_party/blink/renderer/modules/webgl/webgl_context_group.h"
-
-namespace blink {
-
-WebGLContextGroup::WebGLContextGroup() : number_of_context_losses_(0) {}
-
-gpu::gles2::GLES2Interface* WebGLContextGroup::GetAGLInterface() {
-  DCHECK(!contexts_.empty());
-  return (*contexts_.begin())->ContextGL();
-}
-
-void WebGLContextGroup::AddContext(WebGLRenderingContextBase* context) {
-  contexts_.insert(context);
-}
-
-void WebGLContextGroup::LoseContextGroup(
-    WebGLRenderingContextBase::LostContextMode mode,
-    WebGLRenderingContextBase::AutoRecoveryMethod auto_recovery_method) {
-  ++number_of_context_losses_;
-  for (WebGLRenderingContextBase* const context : contexts_)
-    context->LoseContextImpl(mode, auto_recovery_method);
-}
-
-uint32_t WebGLContextGroup::NumberOfContextLosses() const {
-  return number_of_context_losses_;
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_context_group.h b/third_party/blink/renderer/modules/webgl/webgl_context_group.h
deleted file mode 100644
index 4829553..0000000
--- a/third_party/blink/renderer/modules/webgl/webgl_context_group.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2011 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_CONTEXT_GROUP_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_CONTEXT_GROUP_H_
-
-#include "third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h"
-#include "third_party/blink/renderer/platform/bindings/name_client.h"
-#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
-
-namespace blink {
-
-class WebGLContextGroup final : public GarbageCollected<WebGLContextGroup>,
-                                public NameClient {
- public:
-  WebGLContextGroup();
-
-  WebGLContextGroup(const WebGLContextGroup&) = delete;
-  WebGLContextGroup& operator=(const WebGLContextGroup&) = delete;
-
-  ~WebGLContextGroup() final = default;
-
-  void AddContext(WebGLRenderingContextBase*);
-
-  // There's no point in having a removeContext method any more now that
-  // the context group is GarbageCollected. The only time it would be
-  // called would be during WebGLRenderingContext destruction, and at that
-  // time, the context is not allowed to refer back to the context group
-  // since both are on the Oilpan heap.
-
-  gpu::gles2::GLES2Interface* GetAGLInterface();
-
-  void LoseContextGroup(WebGLRenderingContextBase::LostContextMode,
-                        WebGLRenderingContextBase::AutoRecoveryMethod);
-
-  // This counter gets incremented every time context loss is
-  // triggered. Because there's no longer any explicit enumeration of
-  // the objects in a given context group upon context loss, each
-  // object needs to keep track of the context loss count when it was
-  // created, in order to validate itself.
-  uint32_t NumberOfContextLosses() const;
-
-  void Trace(Visitor* visitor) const { visitor->Trace(contexts_); }
-  const char* NameInHeapSnapshot() const override {
-    return "WebGLContextGroup";
-  }
-
- private:
-  friend class WebGLObject;
-
-  uint32_t number_of_context_losses_;
-
-  HeapHashSet<Member<WebGLRenderingContextBase>> contexts_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_CONTEXT_GROUP_H_
diff --git a/third_party/blink/renderer/modules/webgl/webgl_context_object.cc b/third_party/blink/renderer/modules/webgl/webgl_context_object.cc
deleted file mode 100644
index 6d2642e..0000000
--- a/third_party/blink/renderer/modules/webgl/webgl_context_object.cc
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2011 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "third_party/blink/renderer/modules/webgl/webgl_context_object.h"
-
-#include "third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h"
-
-namespace blink {
-
-WebGLContextObject::WebGLContextObject(WebGLRenderingContextBase* context)
-    : WebGLObject(context), context_(context) {}
-
-bool WebGLContextObject::Validate(
-    const WebGLContextGroup*,
-    const WebGLRenderingContextBase* context) const {
-  // The contexts and context groups no longer maintain references to all
-  // the objects they ever created, so there's no way to invalidate them
-  // eagerly during context loss. The invalidation is discovered lazily.
-  return (context == context_ && context_ != nullptr &&
-          CachedNumberOfContextLosses() == context->NumberOfContextLosses());
-}
-
-uint32_t WebGLContextObject::CurrentNumberOfContextLosses() const {
-  if (!context_) {
-    return 0;
-  }
-
-  return context_->NumberOfContextLosses();
-}
-
-gpu::gles2::GLES2Interface* WebGLContextObject::GetAGLInterface() const {
-  if (!context_) {
-    return nullptr;
-  }
-
-  return context_->ContextGL();
-}
-
-void WebGLContextObject::Trace(Visitor* visitor) const {
-  visitor->Trace(context_);
-  WebGLObject::Trace(visitor);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_context_object.h b/third_party/blink/renderer/modules/webgl/webgl_context_object.h
deleted file mode 100644
index c3b4e1b..0000000
--- a/third_party/blink/renderer/modules/webgl/webgl_context_object.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2011 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_CONTEXT_OBJECT_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_CONTEXT_OBJECT_H_
-
-#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
-
-namespace blink {
-
-class WebGLRenderingContextBase;
-
-// WebGLContextObject the base class for objects that are owned by a specific
-// WebGLRenderingContextBase.
-class WebGLContextObject : public WebGLObject {
- public:
-  WebGLRenderingContextBase* Context() const { return context_.Get(); }
-
-  bool Validate(const WebGLContextGroup*,
-                const WebGLRenderingContextBase*) const final;
-
-  void Trace(Visitor*) const override;
-
- protected:
-  explicit WebGLContextObject(WebGLRenderingContextBase*);
-
-  bool HasGroupOrContext() const final { return context_ != nullptr; }
-
-  uint32_t CurrentNumberOfContextLosses() const final;
-
-  gpu::gles2::GLES2Interface* GetAGLInterface() const final;
-
- private:
-  Member<WebGLRenderingContextBase> context_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_CONTEXT_OBJECT_H_
diff --git a/third_party/blink/renderer/modules/webgl/webgl_framebuffer.cc b/third_party/blink/renderer/modules/webgl/webgl_framebuffer.cc
index ce656f7..9b37698 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_framebuffer.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_framebuffer.cc
@@ -47,8 +47,8 @@
   const char* NameInHeapSnapshot() const override { return "WebGLAttachment"; }
 
  private:
-  WebGLSharedObject* Object() const override;
-  bool IsSharedObject(WebGLSharedObject*) const override;
+  WebGLObject* Object() const override;
+  bool IsObject(WebGLObject*) const override;
   bool Valid() const override;
   void OnDetached(gpu::gles2::GLES2Interface*) override;
   void Attach(gpu::gles2::GLES2Interface*,
@@ -70,12 +70,11 @@
     WebGLRenderbuffer* renderbuffer)
     : renderbuffer_(renderbuffer) {}
 
-WebGLSharedObject* WebGLRenderbufferAttachment::Object() const {
+WebGLObject* WebGLRenderbufferAttachment::Object() const {
   return renderbuffer_->Object() ? renderbuffer_.Get() : nullptr;
 }
 
-bool WebGLRenderbufferAttachment::IsSharedObject(
-    WebGLSharedObject* object) const {
+bool WebGLRenderbufferAttachment::IsObject(WebGLObject* object) const {
   return object == renderbuffer_;
 }
 
@@ -117,8 +116,8 @@
   }
 
  private:
-  WebGLSharedObject* Object() const override;
-  bool IsSharedObject(WebGLSharedObject*) const override;
+  WebGLObject* Object() const override;
+  bool IsObject(WebGLObject*) const override;
   bool Valid() const override;
   void OnDetached(gpu::gles2::GLES2Interface*) override;
   void Attach(gpu::gles2::GLES2Interface*,
@@ -145,11 +144,11 @@
                                                GLint layer)
     : texture_(texture), target_(target), level_(level), layer_(layer) {}
 
-WebGLSharedObject* WebGLTextureAttachment::Object() const {
+WebGLObject* WebGLTextureAttachment::Object() const {
   return texture_->Object() ? texture_.Get() : nullptr;
 }
 
-bool WebGLTextureAttachment::IsSharedObject(WebGLSharedObject* object) const {
+bool WebGLTextureAttachment::IsObject(WebGLObject* object) const {
   return object == texture_;
 }
 
@@ -208,14 +207,15 @@
 }
 
 WebGLFramebuffer::WebGLFramebuffer(WebGLRenderingContextBase* ctx, bool opaque)
-    : WebGLContextObject(ctx),
-      object_(0),
+    : WebGLObject(ctx),
       has_ever_been_bound_(false),
       web_gl1_depth_stencil_consistent_(true),
       opaque_(opaque),
       read_buffer_(GL_COLOR_ATTACHMENT0) {
   if (!ctx->isContextLost()) {
-    ctx->ContextGL()->GenFramebuffers(1, &object_);
+    GLuint fbo;
+    ctx->ContextGL()->GenFramebuffers(1, &fbo);
+    SetObject(fbo);
   }
 }
 
@@ -228,7 +228,7 @@
                                                         GLint level,
                                                         GLint layer,
                                                         GLsizei num_views) {
-  DCHECK(object_);
+  DCHECK(HasObject());
   DCHECK(IsBound(target));
 
   if (Context()->isContextLost()) {
@@ -291,7 +291,7 @@
     GLenum target,
     GLenum attachment,
     WebGLRenderbuffer* renderbuffer) {
-  DCHECK(object_);
+  DCHECK(HasObject());
   DCHECK(IsBound(target));
 
   if (Context()->isContextLost()) {
@@ -323,10 +323,10 @@
   }
 }
 
-WebGLSharedObject* WebGLFramebuffer::GetAttachmentObject(
-    GLenum attachment) const {
-  if (!object_)
+WebGLObject* WebGLFramebuffer::GetAttachmentObject(GLenum attachment) const {
+  if (!HasObject()) {
     return nullptr;
+  }
   WebGLAttachment* attachment_object = GetAttachment(attachment);
   return attachment_object ? attachment_object->Object() : nullptr;
 }
@@ -339,10 +339,11 @@
 
 void WebGLFramebuffer::RemoveAttachmentFromBoundFramebuffer(
     GLenum target,
-    WebGLSharedObject* attachment) {
+    WebGLObject* attachment) {
   DCHECK(IsBound(target));
-  if (!object_)
+  if (!HasObject()) {
     return;
+  }
   if (!attachment)
     return;
 
@@ -353,7 +354,7 @@
     check_more = false;
     for (const auto& it : attachments_) {
       WebGLAttachment* attachment_object = it.value.Get();
-      if (attachment_object->IsSharedObject(attachment)) {
+      if (attachment_object->IsObject(attachment)) {
         GLenum attachment_type = it.key;
         switch (attachment_type) {
           case GL_DEPTH_ATTACHMENT:
@@ -434,8 +435,7 @@
   }
 
   // "gl" is null-checked at higher levels.
-  gl->DeleteFramebuffers(1, &object_);
-  object_ = 0;
+  gl->DeleteFramebuffers(1, &Object());
 }
 
 bool WebGLFramebuffer::IsBound(GLenum target) const {
@@ -452,7 +452,7 @@
     default:
       NOTREACHED();
   }
-  return GLuint(bound_fbo) == object_;
+  return GLuint(bound_fbo) == Object();
 }
 
 void WebGLFramebuffer::DrawBuffers(const Vector<GLenum>& bufs) {
@@ -499,7 +499,7 @@
                                              GLint level,
                                              GLint layer) {
   DCHECK(IsBound(target));
-  DCHECK(object_);
+  DCHECK(HasObject());
   RemoveAttachmentInternal(target, attachment);
   if (texture && texture->Object()) {
     attachments_.insert(attachment,
@@ -514,7 +514,7 @@
                                              GLenum attachment,
                                              WebGLRenderbuffer* renderbuffer) {
   DCHECK(IsBound(target));
-  DCHECK(object_);
+  DCHECK(HasObject());
   RemoveAttachmentInternal(target, attachment);
   if (renderbuffer && renderbuffer->Object()) {
     attachments_.insert(
@@ -528,7 +528,7 @@
 void WebGLFramebuffer::RemoveAttachmentInternal(GLenum target,
                                                 GLenum attachment) {
   DCHECK(IsBound(target));
-  DCHECK(object_);
+  DCHECK(HasObject());
 
   WebGLAttachment* attachment_object = GetAttachment(attachment);
   if (attachment_object) {
@@ -639,7 +639,7 @@
 void WebGLFramebuffer::Trace(Visitor* visitor) const {
   visitor->Trace(attachments_);
   visitor->Trace(pls_textures_);
-  WebGLContextObject::Trace(visitor);
+  WebGLObject::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_framebuffer.h b/third_party/blink/renderer/modules/webgl/webgl_framebuffer.h
index f4fb774..b56d82b 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_framebuffer.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_framebuffer.h
@@ -26,8 +26,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_FRAMEBUFFER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_FRAMEBUFFER_H_
 
-#include "third_party/blink/renderer/modules/webgl/webgl_context_object.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 #include "third_party/blink/renderer/platform/bindings/name_client.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
@@ -43,7 +42,7 @@
 class WebGLRenderbuffer;
 class WebGLTexture;
 
-class WebGLFramebuffer final : public WebGLContextObject {
+class WebGLFramebuffer final : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -52,8 +51,8 @@
    public:
     ~WebGLAttachment() override = default;
 
-    virtual WebGLSharedObject* Object() const = 0;
-    virtual bool IsSharedObject(WebGLSharedObject*) const = 0;
+    virtual WebGLObject* Object() const = 0;
+    virtual bool IsObject(WebGLObject*) const = 0;
     virtual bool Valid() const = 0;
     virtual void OnDetached(gpu::gles2::GLES2Interface*) = 0;
     virtual void Attach(gpu::gles2::GLES2Interface*,
@@ -79,8 +78,6 @@
                                         bool has_depth,
                                         bool has_stencil);
 
-  GLuint Object() const { return object_; }
-
   // For a non-multiview attachment, set the num_views parameter to 0. For a
   // multiview attachment, set the layer to the base view index.
   void SetAttachmentForBoundFramebuffer(GLenum target,
@@ -94,8 +91,8 @@
                                         GLenum attachment,
                                         WebGLRenderbuffer*);
   // If an object is attached to the currently bound framebuffer, remove it.
-  void RemoveAttachmentFromBoundFramebuffer(GLenum target, WebGLSharedObject*);
-  WebGLSharedObject* GetAttachmentObject(GLenum) const;
+  void RemoveAttachmentFromBoundFramebuffer(GLenum target, WebGLObject*);
+  WebGLObject* GetAttachmentObject(GLenum) const;
 
   // WebGL 1 specific:
   //   1) can't allow depth_stencil for depth/stencil attachments, and vice
@@ -137,7 +134,6 @@
   const char* NameInHeapSnapshot() const override { return "WebGLFramebuffer"; }
 
  protected:
-  bool HasObject() const override { return object_ != 0; }
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
  private:
@@ -165,8 +161,6 @@
 
   void CommitWebGL1DepthStencilIfConsistent(GLenum target);
 
-  GLuint object_;
-
   typedef HeapHashMap<GLenum, Member<WebGLAttachment>> AttachmentMap;
 
   AttachmentMap attachments_;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_object.cc b/third_party/blink/renderer/modules/webgl/webgl_object.cc
index c8354fd..b2f14157 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_object.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_object.cc
@@ -32,19 +32,29 @@
 namespace blink {
 
 WebGLObject::WebGLObject(WebGLRenderingContextBase* context)
-    : cached_number_of_context_losses_(std::numeric_limits<uint32_t>::max()),
-      attachment_count_(0),
-      marked_for_deletion_(false),
-      destruction_in_progress_(false) {
-  if (context) {
+    : context_(context),
+      cached_number_of_context_losses_(std::numeric_limits<uint32_t>::max()) {
+  if (context_) {
     cached_number_of_context_losses_ = context->NumberOfContextLosses();
   }
 }
 
 WebGLObject::~WebGLObject() = default;
 
-uint32_t WebGLObject::CachedNumberOfContextLosses() const {
-  return cached_number_of_context_losses_;
+bool WebGLObject::Validate(const WebGLRenderingContextBase* context) const {
+  // The contexts and context groups no longer maintain references to all
+  // the objects they ever created, so there's no way to invalidate them
+  // eagerly during context loss. The invalidation is discovered lazily.
+  return (context == context_ && context_ != nullptr &&
+          cached_number_of_context_losses_ == context->NumberOfContextLosses());
+}
+
+void WebGLObject::SetObject(GLuint object) {
+  // SetObject may only be called when this container is in the
+  // uninitialized state: object==0 && marked_for_deletion==false.
+  DCHECK(!object_);
+  DCHECK(!MarkedForDeletion());
+  object_ = object;
 }
 
 void WebGLObject::DeleteObject(gpu::gles2::GLES2Interface* gl) {
@@ -52,21 +62,21 @@
   if (!HasObject())
     return;
 
-  if (!HasGroupOrContext())
+  if (!context_) {
     return;
+  }
 
-  if (CurrentNumberOfContextLosses() != cached_number_of_context_losses_) {
+  if (context_->NumberOfContextLosses() != cached_number_of_context_losses_) {
     // This object has been invalidated.
     return;
   }
 
   if (!attachment_count_) {
     if (!gl)
-      gl = GetAGLInterface();
+      gl = context_->ContextGL();
     if (gl) {
       DeleteObjectImpl(gl);
-      // Ensure the inherited class no longer claims to have a valid object
-      DCHECK(!HasObject());
+      object_ = 0;
     }
   }
 }
@@ -102,4 +112,9 @@
     DeleteObject(gl);
 }
 
+void WebGLObject::Trace(Visitor* visitor) const {
+  visitor->Trace(context_);
+  ScriptWrappable::Trace(visitor);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_object.h b/third_party/blink/renderer/modules/webgl/webgl_object.h
index 25afec6..d11d1cb 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_object.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_object.h
@@ -39,7 +39,6 @@
 
 namespace blink {
 
-class WebGLContextGroup;
 class WebGLRenderingContextBase;
 
 template <typename T>
@@ -75,9 +74,10 @@
   // subclasses via Dispose().
   ~WebGLObject() override;
 
+  WebGLRenderingContextBase* Context() const { return context_.Get(); }
+
   // deleteObject may not always delete the OpenGL resource.  For programs and
   // shaders, deletion is delayed until they are no longer attached.
-  // FIXME: revisit this when resource sharing between contexts are implemented.
   void DeleteObject(gpu::gles2::GLES2Interface*);
 
   void OnAttached() { ++attachment_count_; }
@@ -89,31 +89,29 @@
   bool MarkedForDeletion() { return marked_for_deletion_; }
 
   // True if this object belongs to the group or context.
-  virtual bool Validate(const WebGLContextGroup*,
-                        const WebGLRenderingContextBase*) const = 0;
-  virtual bool HasObject() const = 0;
+  bool Validate(const WebGLRenderingContextBase*) const;
+
+  // A reference is returned so it can be made a pointer for glDelete* calls
+  const GLuint& Object() const { return object_; }
+  bool HasObject() const { return object_ != 0; }
+
+  virtual bool IsRenderbuffer() const { return false; }
+  virtual bool IsTexture() const { return false; }
+
+  void Trace(Visitor*) const override;
 
  protected:
   explicit WebGLObject(WebGLRenderingContextBase*);
 
+  // Must be called only once to set the GL object this JS wrapper wraps.
+  void SetObject(GLuint object);
   // deleteObjectImpl should be only called once to delete the OpenGL resource.
   // After calling deleteObjectImpl, hasObject() should return false.
   virtual void DeleteObjectImpl(gpu::gles2::GLES2Interface*) = 0;
 
-  virtual bool HasGroupOrContext() const = 0;
-
-  // Return the current number of context losses associated with this
-  // object's context group (if it's a shared object), or its
-  // context's context group (if it's a per-context object).
-  virtual uint32_t CurrentNumberOfContextLosses() const = 0;
-
-  uint32_t CachedNumberOfContextLosses() const;
-
   void Detach();
   void DetachAndDeleteObject();
 
-  virtual gpu::gles2::GLES2Interface* GetAGLInterface() const = 0;
-
   // Runs the pre-finalization sequence -- what would be in the destructor
   // of the base class, if it could be. Must be called no more than once.
   void Dispose();
@@ -122,23 +120,25 @@
   bool DestructionInProgress() const;
 
  private:
+  Member<WebGLRenderingContextBase> context_;
+
+  GLuint object_ = 0;
+
   // This was the number of context losses of the object's associated
-  // WebGLContextGroup at the time this object was created. Contexts
-  // no longer refer to all the objects that they ever created, so
-  // it's necessary to check this count when validating each object.
+  // WebGLContext at the time this object was created.
   uint32_t cached_number_of_context_losses_;
 
-  unsigned attachment_count_;
+  unsigned attachment_count_ = 0;
 
   // Indicates whether the WebGL context's deletion function for this object
   // (deleteBuffer, deleteTexture, etc.) has been called. It does *not* indicate
   // whether the underlying OpenGL resource has been destroyed; !HasObject()
   // indicates that.
-  bool marked_for_deletion_;
+  bool marked_for_deletion_ = false;
 
   // Indicates whether the destructor has been entered and we therefore
   // need to be careful in subclasses to not touch other on-heap objects.
-  bool destruction_in_progress_;
+  bool destruction_in_progress_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_program.cc b/third_party/blink/renderer/modules/webgl/webgl_program.cc
index 2da14aeb..0acc2d7 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_program.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_program.cc
@@ -26,13 +26,12 @@
 #include "third_party/blink/renderer/modules/webgl/webgl_program.h"
 
 #include "gpu/command_buffer/client/gles2_interface.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_context_group.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h"
 
 namespace blink {
 
 WebGLProgram::WebGLProgram(WebGLRenderingContextBase* ctx)
-    : WebGLSharedPlatform3DObject(ctx),
+    : WebGLObject(ctx),
       link_status_(false),
       link_count_(0),
       active_transform_feedback_count_(0),
@@ -47,8 +46,7 @@
 WebGLProgram::~WebGLProgram() = default;
 
 void WebGLProgram::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
-  gl->DeleteProgram(object_);
-  object_ = 0;
+  gl->DeleteProgram(Object());
   if (!DestructionInProgress()) {
     if (vertex_shader_) {
       vertex_shader_->OnDetached(gl);
@@ -71,7 +69,7 @@
   gpu::gles2::GLES2Interface* gl = context->ContextGL();
   // If gl is nullptr, context has been lost.
   if (gl) {
-    gl->GetProgramiv(object_, GL_COMPLETION_STATUS_KHR, &completed);
+    gl->GetProgramiv(Object(), GL_COMPLETION_STATUS_KHR, &completed);
   }
 
   return completed;
@@ -142,15 +140,16 @@
 void WebGLProgram::CacheInfoIfNeeded(WebGLRenderingContextBase* context) {
   if (info_valid_)
     return;
-  if (!object_)
+  if (!HasObject()) {
     return;
+  }
   gpu::gles2::GLES2Interface* gl = context->ContextGL();
   if (!gl) {
     // Context has been lost.
     return;
   }
   GLint link_status = 0;
-  gl->GetProgramiv(object_, GL_LINK_STATUS, &link_status);
+  gl->GetProgramiv(Object(), GL_LINK_STATUS, &link_status);
   setLinkStatus(link_status);
 }
 
@@ -169,7 +168,7 @@
 void WebGLProgram::Trace(Visitor* visitor) const {
   visitor->Trace(vertex_shader_);
   visitor->Trace(fragment_shader_);
-  WebGLSharedPlatform3DObject::Trace(visitor);
+  WebGLObject::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_program.h b/third_party/blink/renderer/modules/webgl/webgl_program.h
index 784b77eb0..f49c5ba 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_program.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_program.h
@@ -26,12 +26,12 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_PROGRAM_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_PROGRAM_H_
 
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_shader.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h"
 
 namespace blink {
 
-class WebGLProgram final : public WebGLSharedPlatform3DObject {
+class WebGLProgram final : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -76,8 +76,6 @@
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
  private:
-  bool IsProgram() const override { return true; }
-
   void CacheInfoIfNeeded(WebGLRenderingContextBase*);
 
   GLint link_status_;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_query.cc b/third_party/blink/renderer/modules/webgl/webgl_query.cc
index 8955420..161e09bd 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_query.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_query.cc
@@ -12,7 +12,7 @@
 namespace blink {
 
 WebGLQuery::WebGLQuery(WebGL2RenderingContextBase* ctx)
-    : WebGLSharedPlatform3DObject(ctx),
+    : WebGLObject(ctx),
       target_(0),
       can_update_availability_(false),
       query_result_available_(false),
@@ -28,14 +28,13 @@
 WebGLQuery::~WebGLQuery() = default;
 
 void WebGLQuery::SetTarget(GLenum target) {
-  DCHECK(Object());
+  DCHECK(HasObject());
   DCHECK(!target_);
   target_ = target;
 }
 
 void WebGLQuery::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
-  gl->DeleteQueriesEXT(1, &object_);
-  object_ = 0;
+  gl->DeleteQueriesEXT(1, &Object());
 }
 
 void WebGLQuery::ResetCachedResult() {
diff --git a/third_party/blink/renderer/modules/webgl/webgl_query.h b/third_party/blink/renderer/modules/webgl/webgl_query.h
index b1b81e0d..bf4648e 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_query.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_query.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_QUERY_H_
 
 #include "base/task/single_thread_task_runner.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
 
 namespace gpu {
@@ -19,7 +19,7 @@
 
 class WebGL2RenderingContextBase;
 
-class WebGLQuery : public WebGLSharedPlatform3DObject {
+class WebGLQuery : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -40,8 +40,6 @@
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
  private:
-  bool IsQuery() const override { return true; }
-
   void ScheduleAllowAvailabilityUpdate();
   void AllowAvailabilityUpdate();
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.cc b/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.cc
index 6898177..efca7fb 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.cc
@@ -31,7 +31,7 @@
 namespace blink {
 
 WebGLRenderbuffer::WebGLRenderbuffer(WebGLRenderingContextBase* ctx)
-    : WebGLSharedPlatform3DObject(ctx),
+    : WebGLObject(ctx),
       internal_format_(GL_RGBA4),
       width_(0),
       height_(0),
@@ -47,8 +47,7 @@
 WebGLRenderbuffer::~WebGLRenderbuffer() = default;
 
 void WebGLRenderbuffer::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
-  gl->DeleteRenderbuffers(1, &object_);
-  object_ = 0;
+  gl->DeleteRenderbuffers(1, &Object());
 }
 
 int WebGLRenderbuffer::UpdateMultisampleState(bool multisampled) {
@@ -62,7 +61,7 @@
 }
 
 void WebGLRenderbuffer::Trace(Visitor* visitor) const {
-  WebGLSharedPlatform3DObject::Trace(visitor);
+  WebGLObject::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.h b/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.h
index af8814d..8f07e376 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_renderbuffer.h
@@ -26,11 +26,11 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_RENDERBUFFER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_RENDERBUFFER_H_
 
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 
 namespace blink {
 
-class WebGLRenderbuffer final : public WebGLSharedPlatform3DObject {
+class WebGLRenderbuffer final : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index 5cf40332..1ef7fa4 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -104,7 +104,6 @@
 #include "third_party/blink/renderer/modules/webgl/webgl_compressed_texture_s3tc_srgb.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_context_attribute_helpers.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_context_event.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_context_group.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_debug_renderer_info.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_debug_shaders.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_depth_texture.h"
@@ -1264,7 +1263,6 @@
                              context_type == Platform::kWebGL2ContextType
                                  ? CanvasRenderingAPI::kWebgl2
                                  : CanvasRenderingAPI::kWebgl),
-      context_group_(MakeGarbageCollected<WebGLContextGroup>()),
       dispatch_context_lost_event_timer_(
           task_runner,
           this,
@@ -1280,8 +1278,6 @@
 
   xr_compatible_ = requested_attributes.xr_compatible;
 
-  context_group_->AddContext(this);
-
   max_viewport_dims_ = {};
   context_provider->ContextGL()->GetIntegerv(GL_MAX_VIEWPORT_DIMS,
                                              max_viewport_dims_.data());
@@ -2796,7 +2792,7 @@
 bool WebGLRenderingContextBase::DeleteObject(WebGLObject* object) {
   if (isContextLost() || !object)
     return false;
-  if (!object->Validate(ContextGroup(), this)) {
+  if (!object->Validate(this)) {
     SynthesizeGLError(GL_INVALID_OPERATION, "delete",
                       "object does not belong to this context");
     return false;
@@ -3017,7 +3013,7 @@
                       "attempt to use a deleted object");
     return false;
   }
-  if (!object->Validate(ContextGroup(), this)) {
+  if (!object->Validate(this)) {
     SynthesizeGLError(GL_INVALID_OPERATION, function_name,
                       "object does not belong to this context");
     return false;
@@ -3045,7 +3041,7 @@
                       "attempt to use a deleted object");
     return false;
   }
-  if (!object->Validate(ContextGroup(), this)) {
+  if (!object->Validate(this)) {
     SynthesizeGLError(GL_INVALID_OPERATION, function_name,
                       "object does not belong to this context");
     return false;
@@ -3502,7 +3498,7 @@
     return ScriptValue::CreateNull(script_state->GetIsolate());
   }
 
-  WebGLSharedObject* attachment_object =
+  WebGLObject* attachment_object =
       framebuffer_binding_->GetAttachmentObject(attachment);
   if (!attachment_object) {
     if (pname == GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)
@@ -4666,8 +4662,9 @@
 }
 
 bool WebGLRenderingContextBase::isBuffer(WebGLBuffer* buffer) {
-  if (!buffer || isContextLost() || !buffer->Validate(ContextGroup(), this))
+  if (!buffer || isContextLost() || !buffer->Validate(this)) {
     return false;
+  }
 
   if (!buffer->HasEverBeenBound())
     return false;
@@ -4694,9 +4691,9 @@
 }
 
 bool WebGLRenderingContextBase::isFramebuffer(WebGLFramebuffer* framebuffer) {
-  if (!framebuffer || isContextLost() ||
-      !framebuffer->Validate(ContextGroup(), this))
+  if (!framebuffer || isContextLost() || !framebuffer->Validate(this)) {
     return false;
+  }
 
   if (!framebuffer->HasEverBeenBound())
     return false;
@@ -4707,8 +4704,9 @@
 }
 
 bool WebGLRenderingContextBase::isProgram(WebGLProgram* program) {
-  if (!program || isContextLost() || !program->Validate(ContextGroup(), this))
+  if (!program || isContextLost() || !program->Validate(this)) {
     return false;
+  }
 
   // OpenGL ES special-cases the behavior of program objects; if they're deleted
   // while attached to the current context state, glIsProgram is supposed to
@@ -4719,9 +4717,9 @@
 
 bool WebGLRenderingContextBase::isRenderbuffer(
     WebGLRenderbuffer* renderbuffer) {
-  if (!renderbuffer || isContextLost() ||
-      !renderbuffer->Validate(ContextGroup(), this))
+  if (!renderbuffer || isContextLost() || !renderbuffer->Validate(this)) {
     return false;
+  }
 
   if (!renderbuffer->HasEverBeenBound())
     return false;
@@ -4732,8 +4730,9 @@
 }
 
 bool WebGLRenderingContextBase::isShader(WebGLShader* shader) {
-  if (!shader || isContextLost() || !shader->Validate(ContextGroup(), this))
+  if (!shader || isContextLost() || !shader->Validate(this)) {
     return false;
+  }
 
   // OpenGL ES special-cases the behavior of shader objects; if they're deleted
   // while attached to a program, glIsShader is supposed to still return true.
@@ -4743,8 +4742,9 @@
 }
 
 bool WebGLRenderingContextBase::isTexture(WebGLTexture* texture) {
-  if (!texture || isContextLost() || !texture->Validate(ContextGroup(), this))
+  if (!texture || isContextLost() || !texture->Validate(this)) {
     return false;
+  }
 
   if (!texture->HasEverBeenBound())
     return false;
@@ -7118,12 +7118,14 @@
     return;
   }
 
-  context_group_->LoseContextGroup(mode, auto_recovery_method);
+  LoseContextImpl(mode, auto_recovery_method);
 }
 
 void WebGLRenderingContextBase::LoseContextImpl(
     WebGLRenderingContextBase::LostContextMode mode,
     AutoRecoveryMethod auto_recovery_method) {
+  number_of_context_losses_++;
+
   if (isContextLost())
     return;
 
@@ -7215,7 +7217,7 @@
 }
 
 uint32_t WebGLRenderingContextBase::NumberOfContextLosses() const {
-  return context_group_->NumberOfContextLosses();
+  return number_of_context_losses_;
 }
 
 cc::Layer* WebGLRenderingContextBase::CcLayer() const {
@@ -8873,7 +8875,6 @@
 }
 
 void WebGLRenderingContextBase::Trace(Visitor* visitor) const {
-  visitor->Trace(context_group_);
   visitor->Trace(dispatch_context_lost_event_timer_);
   visitor->Trace(restore_timer_);
   visitor->Trace(bound_array_buffer_);
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
index 2e0a148..3e74a3b 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -103,7 +103,6 @@
 class WebGLCompressedTexturePVRTC;
 class WebGLCompressedTextureS3TC;
 class WebGLCompressedTextureS3TCsRGB;
-class WebGLContextGroup;
 class WebGLContextObject;
 class WebGLDebugShaders;
 class WebGLDrawBuffers;
@@ -597,7 +596,6 @@
     return d->ContextProvider()->SharedImageInterface();
   }
 
-  WebGLContextGroup* ContextGroup() const { return context_group_.Get(); }
   Extensions3DUtil* ExtensionsUtil();
 
   void Reshape(int width, int height) override;
@@ -835,8 +833,6 @@
   // to the back-buffer of m_context.
   scoped_refptr<DrawingBuffer> drawing_buffer_;
 
-  Member<WebGLContextGroup> context_group_;
-
   LostContextMode context_lost_mode_ = kNotLostContext;
   AutoRecoveryMethod auto_recovery_method_ = kManual;
   // Dispatches a context lost event once it is determined that one is needed.
@@ -2024,6 +2020,8 @@
 
   bool has_been_drawn_to_ = false;
 
+  uint32_t number_of_context_losses_ = 0;
+
   // Tracks if the context has ever called glBeginPixelLocalStorageANGLE. If it
   // has, we need to start using the pixel local storage interrupt mechanism
   // when we take over the client's context.
diff --git a/third_party/blink/renderer/modules/webgl/webgl_sampler.cc b/third_party/blink/renderer/modules/webgl/webgl_sampler.cc
index d53bea7..2ed1a52 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_sampler.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_sampler.cc
@@ -9,8 +9,7 @@
 
 namespace blink {
 
-WebGLSampler::WebGLSampler(WebGL2RenderingContextBase* ctx)
-    : WebGLSharedPlatform3DObject(ctx) {
+WebGLSampler::WebGLSampler(WebGL2RenderingContextBase* ctx) : WebGLObject(ctx) {
   GLuint sampler;
   if (!ctx->isContextLost()) {
     ctx->ContextGL()->GenSamplers(1, &sampler);
@@ -21,8 +20,7 @@
 WebGLSampler::~WebGLSampler() = default;
 
 void WebGLSampler::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
-  gl->DeleteSamplers(1, &object_);
-  object_ = 0;
+  gl->DeleteSamplers(1, &Object());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_sampler.h b/third_party/blink/renderer/modules/webgl/webgl_sampler.h
index c3ef588..625a632 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_sampler.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_sampler.h
@@ -5,13 +5,13 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SAMPLER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SAMPLER_H_
 
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 
 namespace blink {
 
 class WebGL2RenderingContextBase;
 
-class WebGLSampler : public WebGLSharedPlatform3DObject {
+class WebGLSampler : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -20,9 +20,6 @@
 
  protected:
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
-
- private:
-  bool IsSampler() const override { return true; }
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_shader.cc b/third_party/blink/renderer/modules/webgl/webgl_shader.cc
index a9f9514..0f2b17d 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_shader.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_shader.cc
@@ -31,7 +31,7 @@
 namespace blink {
 
 WebGLShader::WebGLShader(WebGLRenderingContextBase* ctx, GLenum type)
-    : WebGLSharedPlatform3DObject(ctx), type_(type), source_("") {
+    : WebGLObject(ctx), type_(type), source_("") {
   if (!ctx->isContextLost()) {
     SetObject(ctx->ContextGL()->CreateShader(type));
   }
@@ -40,8 +40,7 @@
 WebGLShader::~WebGLShader() = default;
 
 void WebGLShader::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
-  gl->DeleteShader(object_);
-  object_ = 0;
+  gl->DeleteShader(Object());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_shader.h b/third_party/blink/renderer/modules/webgl/webgl_shader.h
index 5344a70..0ef9e0f 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_shader.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_shader.h
@@ -26,12 +26,12 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SHADER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SHADER_H_
 
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
-class WebGLShader final : public WebGLSharedPlatform3DObject {
+class WebGLShader final : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -47,8 +47,6 @@
  private:
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
-  bool IsShader() const override { return true; }
-
   GLenum type_;
   String source_;
 };
diff --git a/third_party/blink/renderer/modules/webgl/webgl_shared_object.cc b/third_party/blink/renderer/modules/webgl/webgl_shared_object.cc
deleted file mode 100644
index 7aea8ca..0000000
--- a/third_party/blink/renderer/modules/webgl/webgl_shared_object.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2011 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_object.h"
-
-#include "third_party/blink/renderer/modules/webgl/webgl_context_group.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h"
-
-namespace blink {
-
-WebGLSharedObject::WebGLSharedObject(WebGLRenderingContextBase* context)
-    : WebGLObject(context), context_group_(nullptr) {
-  if (context) {
-    context_group_ = context->ContextGroup();
-  }
-}
-
-bool WebGLSharedObject::Validate(const WebGLContextGroup* context_group,
-                                 const WebGLRenderingContextBase*) const {
-  // The contexts and context groups no longer maintain references to all
-  // the objects they ever created, so there's no way to invalidate them
-  // eagerly during context loss. The invalidation is discovered lazily.
-  return (context_group == context_group_ && context_group != nullptr &&
-          CachedNumberOfContextLosses() ==
-              context_group->NumberOfContextLosses());
-}
-
-uint32_t WebGLSharedObject::CurrentNumberOfContextLosses() const {
-  if (!context_group_) {
-    return 0;
-  }
-
-  return context_group_->NumberOfContextLosses();
-}
-
-gpu::gles2::GLES2Interface* WebGLSharedObject::GetAGLInterface() const {
-  if (!context_group_) {
-    return nullptr;
-  }
-
-  return context_group_->GetAGLInterface();
-}
-
-void WebGLSharedObject::Trace(Visitor* visitor) const {
-  visitor->Trace(context_group_);
-  WebGLObject::Trace(visitor);
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_shared_object.h b/third_party/blink/renderer/modules/webgl/webgl_shared_object.h
deleted file mode 100644
index 280405f..0000000
--- a/third_party/blink/renderer/modules/webgl/webgl_shared_object.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2011 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SHARED_OBJECT_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SHARED_OBJECT_H_
-
-#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
-
-namespace blink {
-
-class WebGLContextGroup;
-class WebGLRenderingContextBase;
-
-// WebGLSharedObject is the base class for objects that can be shared by
-// multiple WebGLRenderingContexts.
-class WebGLSharedObject : public WebGLObject {
- public:
-  WebGLContextGroup* ContextGroup() const { return context_group_.Get(); }
-
-  virtual bool IsBuffer() const { return false; }
-  virtual bool IsProgram() const { return false; }
-  virtual bool IsQuery() const { return false; }
-  virtual bool IsRenderbuffer() const { return false; }
-  virtual bool IsSampler() const { return false; }
-  virtual bool IsShader() const { return false; }
-  virtual bool IsSync() const { return false; }
-  virtual bool IsTexture() const { return false; }
-
-  bool Validate(const WebGLContextGroup* context_group,
-                const WebGLRenderingContextBase*) const final;
-
-  void Trace(Visitor*) const override;
-
- protected:
-  explicit WebGLSharedObject(WebGLRenderingContextBase*);
-
-  bool HasGroupOrContext() const final { return context_group_ != nullptr; }
-
-  uint32_t CurrentNumberOfContextLosses() const final;
-
-  gpu::gles2::GLES2Interface* GetAGLInterface() const final;
-
- private:
-  Member<WebGLContextGroup> context_group_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SHARED_OBJECT_H_
diff --git a/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.cc b/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.cc
deleted file mode 100644
index 32bc3f9..0000000
--- a/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2015 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h"
-
-#include "third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h"
-
-namespace blink {
-
-WebGLSharedPlatform3DObject::WebGLSharedPlatform3DObject(
-    WebGLRenderingContextBase* ctx)
-    : WebGLSharedObject(ctx), object_(0) {}
-
-void WebGLSharedPlatform3DObject::SetObject(GLuint object) {
-  // SetObject may only be called when this container is in the
-  // uninitialized state: object==0 && marked_for_deletion==false.
-  DCHECK(!object_);
-  DCHECK(!MarkedForDeletion());
-  object_ = object;
-}
-
-bool WebGLSharedPlatform3DObject::HasObject() const {
-  return object_ != 0;
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h b/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h
deleted file mode 100644
index 8534f4738..0000000
--- a/third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2015 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SHARED_PLATFORM_3D_OBJECT_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SHARED_PLATFORM_3D_OBJECT_H_
-
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_object.h"
-
-namespace blink {
-
-class WebGLRenderingContextBase;
-
-class WebGLSharedPlatform3DObject : public WebGLSharedObject {
- public:
-  GLuint Object() const { return object_; }
-  void SetObject(GLuint);
-
- protected:
-  explicit WebGLSharedPlatform3DObject(WebGLRenderingContextBase*);
-
-  bool HasObject() const override;
-
-  GLuint object_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SHARED_PLATFORM_3D_OBJECT_H_
diff --git a/third_party/blink/renderer/modules/webgl/webgl_sync.cc b/third_party/blink/renderer/modules/webgl/webgl_sync.cc
index 4c5ac9b..ca4f88b 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_sync.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_sync.cc
@@ -14,11 +14,11 @@
 WebGLSync::WebGLSync(WebGL2RenderingContextBase* ctx,
                      GLuint object,
                      GLenum object_type)
-    : WebGLSharedObject(ctx),
+    : WebGLObject(ctx),
       sync_status_(GL_UNSIGNALED),
-      object_(object),
       object_type_(object_type),
       task_runner_(ctx->GetContextTaskRunner()) {
+  SetObject(object);
   ScheduleAllowCacheUpdate();
 }
 
@@ -38,7 +38,7 @@
   // We can only update the cached result when control returns to the browser.
   allow_cache_update_ = false;
   GLuint value = 0;
-  gl->GetQueryObjectuivEXT(object_, GL_QUERY_RESULT_AVAILABLE, &value);
+  gl->GetQueryObjectuivEXT(Object(), GL_QUERY_RESULT_AVAILABLE, &value);
   if (value == GL_TRUE) {
     sync_status_ = GL_SIGNALED;
   } else {
@@ -79,8 +79,7 @@
 }
 
 void WebGLSync::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
-  gl->DeleteQueriesEXT(1, &object_);
-  object_ = 0;
+  gl->DeleteQueriesEXT(1, &Object());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_sync.h b/third_party/blink/renderer/modules/webgl/webgl_sync.h
index 2e92e777..5e0f6e8 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_sync.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_sync.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_SYNC_H_
 
 #include "base/task/single_thread_task_runner.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
 
 namespace gpu {
@@ -19,14 +19,12 @@
 
 class WebGL2RenderingContextBase;
 
-class WebGLSync : public WebGLSharedObject {
+class WebGLSync : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
   ~WebGLSync() override;
 
-  GLuint Object() const { return object_; }
-
   void UpdateCache(gpu::gles2::GLES2Interface*);
   GLint GetCachedResult(GLenum pname);
   bool IsSignaled() const;
@@ -34,14 +32,9 @@
  protected:
   WebGLSync(WebGL2RenderingContextBase*, GLuint, GLenum object_type);
 
-  bool HasObject() const override { return object_ != 0; }
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
-  GLenum ObjectType() const { return object_type_; }
-
  private:
-  bool IsSync() const override { return true; }
-
   void ScheduleAllowCacheUpdate();
   void AllowCacheUpdate();
 
@@ -49,7 +42,6 @@
   // Initialized in cpp file to avoid including gl3.h in this header.
   GLint sync_status_;
 
-  GLuint object_;
   GLenum object_type_;
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_texture.cc b/third_party/blink/renderer/modules/webgl/webgl_texture.cc
index 1bff9e5..282bbc2c 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_texture.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_texture.cc
@@ -31,7 +31,7 @@
 namespace blink {
 
 WebGLTexture::WebGLTexture(WebGLRenderingContextBase* ctx)
-    : WebGLSharedPlatform3DObject(ctx), target_(0) {
+    : WebGLObject(ctx), target_(0) {
   if (!ctx->isContextLost()) {
     GLuint texture;
     ctx->ContextGL()->GenTextures(1, &texture);
@@ -42,7 +42,7 @@
 WebGLTexture::WebGLTexture(WebGLRenderingContextBase* ctx,
                            GLuint texture,
                            GLenum target)
-    : WebGLSharedPlatform3DObject(ctx), target_(target) {
+    : WebGLObject(ctx), target_(target) {
   SetObject(texture);
 }
 
@@ -58,8 +58,7 @@
 }
 
 void WebGLTexture::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
-  gl->DeleteTextures(1, &object_);
-  object_ = 0;
+  gl->DeleteTextures(1, &Object());
 }
 
 int WebGLTexture::MapTargetToIndex(GLenum target) const {
diff --git a/third_party/blink/renderer/modules/webgl/webgl_texture.h b/third_party/blink/renderer/modules/webgl/webgl_texture.h
index df94247..8f30095 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_texture.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_texture.h
@@ -29,12 +29,12 @@
 #include "base/time/time.h"
 #include "media/base/video_frame.h"
 #include "third_party/blink/public/platform/web_media_player.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_shared_platform_3d_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace blink {
 
-class WebGLTexture : public WebGLSharedPlatform3DObject {
+class WebGLTexture : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
diff --git a/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.cc b/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.cc
index 1307fdef..5586583 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.cc
@@ -12,9 +12,8 @@
 namespace blink {
 
 WebGLTimerQueryEXT::WebGLTimerQueryEXT(WebGLRenderingContextBase* ctx)
-    : WebGLContextObject(ctx),
+    : WebGLObject(ctx),
       target_(0),
-      query_id_(0),
       can_update_availability_(false),
       query_result_available_(false),
       query_result_(0),
@@ -23,7 +22,9 @@
     return;
   }
 
-  Context()->ContextGL()->GenQueriesEXT(1, &query_id_);
+  GLuint query = 0;
+  ctx->ContextGL()->GenQueriesEXT(1, &query);
+  SetObject(query);
 }
 
 WebGLTimerQueryEXT::~WebGLTimerQueryEXT() = default;
@@ -74,8 +75,7 @@
 }
 
 void WebGLTimerQueryEXT::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
-  gl->DeleteQueriesEXT(1, &query_id_);
-  query_id_ = 0;
+  gl->DeleteQueriesEXT(1, &Object());
 }
 
 void WebGLTimerQueryEXT::ScheduleAllowAvailabilityUpdate() {
diff --git a/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.h b/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.h
index 4571fa4..759bc36 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_timer_query_ext.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_TIMER_QUERY_EXT_H_
 
 #include "base/task/single_thread_task_runner.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_context_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h"
 
 namespace gpu {
@@ -17,7 +17,7 @@
 
 namespace blink {
 
-class WebGLTimerQueryEXT : public WebGLContextObject {
+class WebGLTimerQueryEXT : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -25,8 +25,6 @@
   ~WebGLTimerQueryEXT() override;
 
   void SetTarget(GLenum target) { target_ = target; }
-
-  GLuint Object() const { return query_id_; }
   bool HasTarget() const { return target_ != 0; }
   GLenum Target() const { return target_; }
 
@@ -37,14 +35,12 @@
   GLuint64 GetQueryResult();
 
  private:
-  bool HasObject() const override { return query_id_ != 0; }
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
   void ScheduleAllowAvailabilityUpdate();
   void AllowAvailabilityUpdate();
 
   GLenum target_;
-  GLuint query_id_;
 
   bool can_update_availability_;
   bool query_result_available_;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.cc b/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.cc
index 5f493f0..e904eca 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.cc
@@ -13,8 +13,7 @@
     WebGL2RenderingContextBase* ctx,
     TFType type,
     GLint max_transform_feedback_separate_attribs)
-    : WebGLContextObject(ctx),
-      object_(0),
+    : WebGLObject(ctx),
       type_(type),
       target_(0),
       program_(nullptr),
@@ -34,7 +33,7 @@
     case TFType::kUser: {
       GLuint tf;
       ctx->ContextGL()->GenTransformFeedbacks(1, &tf);
-      object_ = tf;
+      SetObject(tf);
       break;
     }
   }
@@ -56,8 +55,7 @@
     case TFType::kDefault:
       break;
     case TFType::kUser:
-      gl->DeleteTransformFeedbacks(1, &object_);
-      object_ = 0;
+      gl->DeleteTransformFeedbacks(1, &Object());
       break;
   }
 
@@ -144,7 +142,7 @@
 void WebGLTransformFeedback::Trace(Visitor* visitor) const {
   visitor->Trace(bound_indexed_transform_feedback_buffers_);
   visitor->Trace(program_);
-  WebGLContextObject::Trace(visitor);
+  WebGLObject::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.h b/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.h
index 3e77dbd..39af2a1 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_transform_feedback.h
@@ -5,7 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_TRANSFORM_FEEDBACK_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_TRANSFORM_FEEDBACK_H_
 
-#include "third_party/blink/renderer/modules/webgl/webgl_context_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 #include "third_party/blink/renderer/modules/webgl/webgl_program.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 
@@ -14,7 +14,7 @@
 class WebGL2RenderingContextBase;
 class WebGLBuffer;
 
-class WebGLTransformFeedback : public WebGLContextObject {
+class WebGLTransformFeedback : public WebGLObject {
   DEFINE_WRAPPERTYPEINFO();
 
  public:
@@ -29,14 +29,12 @@
       GLint max_transform_feedback_separate_attribs);
   ~WebGLTransformFeedback() override;
 
-  GLuint Object() const { return object_; }
-
   bool IsDefaultObject() const { return type_ == TFType::kDefault; }
 
   GLenum GetTarget() const { return target_; }
   void SetTarget(GLenum);
 
-  bool HasEverBeenBound() const { return object_ && target_; }
+  bool HasEverBeenBound() const { return HasObject() && target_; }
 
   WebGLProgram* GetProgram() const { return program_.Get(); }
   void SetProgram(WebGLProgram*);
@@ -77,11 +75,8 @@
 
  private:
   void DispatchDetached(gpu::gles2::GLES2Interface*);
-  bool HasObject() const override { return object_ != 0; }
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
-  GLuint object_;
-
   TFType type_;
   GLenum target_;
 
diff --git a/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.cc b/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.cc
index 5799290..94cbef1 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_unowned_texture.cc
@@ -20,13 +20,12 @@
   // Note that this will suppress the rest of the logic found in
   // WebGLObject::DeleteObject(), since one of the first things that the method
   // does is a check to see if |object_| is valid.
-  object_ = 0;
+  SetObject(0);
 }
 
 void WebGLUnownedTexture::DeleteObjectImpl(gpu::gles2::GLES2Interface* gl) {
   // Normally, we would invoke gl->DeleteTextures() here, but
   // WebGLUnownedTexture does not own its texture name. Just zero it out.
-  object_ = 0;
 }
 
 WebGLUnownedTexture::~WebGLUnownedTexture() = default;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.cc b/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.cc
index e71c9da..1fddc23 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.cc
@@ -13,8 +13,7 @@
     WebGLRenderingContextBase* ctx,
     VaoType type,
     GLint max_vertex_attribs)
-    : WebGLContextObject(ctx),
-      object_(0),
+    : WebGLObject(ctx),
       type_(type),
       has_ever_been_bound_(false),
       is_all_enabled_attrib_buffer_bound_(true) {
@@ -31,9 +30,12 @@
   switch (type_) {
     case kVaoTypeDefault:
       break;
-    default:
-      Context()->ContextGL()->GenVertexArraysOES(1, &object_);
+    default: {
+      GLuint vao = 0;
+      ctx->ContextGL()->GenVertexArraysOES(1, &vao);
+      SetObject(vao);
       break;
+    }
   }
 }
 
@@ -56,8 +58,7 @@
     case kVaoTypeDefault:
       break;
     default:
-      gl->DeleteVertexArraysOES(1, &object_);
-      object_ = 0;
+      gl->DeleteVertexArraysOES(1, &Object());
       break;
   }
 
@@ -127,7 +128,7 @@
 void WebGLVertexArrayObjectBase::Trace(Visitor* visitor) const {
   visitor->Trace(bound_element_array_buffer_);
   visitor->Trace(array_buffer_list_);
-  WebGLContextObject::Trace(visitor);
+  WebGLObject::Trace(visitor);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.h b/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.h
index 7bd54f01..10407f4 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_vertex_array_object_base.h
@@ -6,13 +6,13 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBGL_WEBGL_VERTEX_ARRAY_OBJECT_BASE_H_
 
 #include "third_party/blink/renderer/modules/webgl/webgl_buffer.h"
-#include "third_party/blink/renderer/modules/webgl/webgl_context_object.h"
+#include "third_party/blink/renderer/modules/webgl/webgl_object.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
 
-class WebGLVertexArrayObjectBase : public WebGLContextObject {
+class WebGLVertexArrayObjectBase : public WebGLObject {
  public:
   enum VaoType {
     kVaoTypeDefault,
@@ -21,8 +21,6 @@
 
   ~WebGLVertexArrayObjectBase() override;
 
-  GLuint Object() const { return object_; }
-
   bool IsDefaultObject() const { return type_ == kVaoTypeDefault; }
 
   bool HasEverBeenBound() const { return Object() && has_ever_been_bound_; }
@@ -53,13 +51,10 @@
 
  private:
   void DispatchDetached(gpu::gles2::GLES2Interface*);
-  bool HasObject() const override { return object_ != 0; }
   void DeleteObjectImpl(gpu::gles2::GLES2Interface*) override;
 
   void UpdateAttribBufferBoundStatus();
 
-  GLuint object_;
-
   VaoType type_;
   bool has_ever_been_bound_;
   Member<WebGLBuffer> bound_element_array_buffer_;
diff --git a/third_party/blink/renderer/modules/xr/xr_cube_map.cc b/third_party/blink/renderer/modules/xr/xr_cube_map.cc
index ecd50e5..c323a2e31 100644
--- a/third_party/blink/renderer/modules/xr/xr_cube_map.cc
+++ b/third_party/blink/renderer/modules/xr/xr_cube_map.cc
@@ -91,7 +91,6 @@
   DCHECK(texture);
   DCHECK(!texture->HasEverBeenBound() ||
          texture->GetTarget() == GL_TEXTURE_CUBE_MAP);
-  DCHECK(texture->ContextGroup() == context->ContextGroup());
 
   auto* gl = context->ContextGL();
   texture->SetTarget(GL_TEXTURE_CUBE_MAP);
diff --git a/third_party/blink/renderer/platform/graphics/image_decoder_wrapper.cc b/third_party/blink/renderer/platform/graphics/image_decoder_wrapper.cc
index 6576d55..6327d8c2 100644
--- a/third_party/blink/renderer/platform/graphics/image_decoder_wrapper.cc
+++ b/third_party/blink/renderer/platform/graphics/image_decoder_wrapper.cc
@@ -102,9 +102,7 @@
 }  // namespace
 
 bool ImageDecoderWrapper::Decode(ImageDecoderFactory* factory,
-                                 wtf_size_t* frame_count,
                                  bool* has_alpha) {
-  DCHECK(frame_count);
   DCHECK(has_alpha);
 
   ImageDecoder* decoder = nullptr;
@@ -124,15 +122,14 @@
     decoder = new_decoder.get();
   }
 
-  // For multi-frame image decoders, we need to know how many frames are
-  // in that image in order to release the decoder when all frames are
-  // decoded. FrameCount() is reliable only if all data is received and set in
-  // decoder, particularly with GIF.
-  if (all_data_received_)
-    *frame_count = decoder->FrameCount();
+  // For multi-frame image decoders, we need to know how many frames are in
+  // that image in order to release the decoder when all frames are decoded.
+  // `FrameCount()` is reliable only if all data is received and set in decoder,
+  // particularly with GIF.
+  wtf_size_t frame_count = all_data_received_ ? decoder->FrameCount() : 0u;
 
   const bool decode_to_external_memory =
-      ShouldDecodeToExternalMemory(*frame_count, resume_decoding);
+      ShouldDecodeToExternalMemory(frame_count, resume_decoding);
 
   ExternalMemoryAllocator external_memory_allocator(pixmap_);
   if (decode_to_external_memory)
@@ -189,8 +186,7 @@
   // re-decoding of all frames in the dependency chain).
   const bool frame_was_completely_decoded =
       frame->GetStatus() == ImageFrame::kFrameComplete || all_data_received_;
-  PurgeAllFramesIfNecessary(decoder, frame_was_completely_decoded,
-                            *frame_count);
+  PurgeAllFramesIfNecessary(decoder, frame_was_completely_decoded, frame_count);
 
   const bool should_remove_decoder = ShouldRemoveDecoder(
       frame_was_completely_decoded, decode_to_external_memory);
@@ -233,9 +229,9 @@
     return true;
   }
 
-  // TODO (scroggo): If !is_multi_frame_ && new_decoder && frame_count_, it
-  // should always be the case that 1u == frame_count_. But it looks like it
-  // is currently possible for frame_count_ to be another value.
+  // TODO (scroggo): If !is_multi_frame_ && new_decoder && frame_count, it
+  // should always be the case that 1u == frame_count. But it looks like it is
+  // currently possible for frame_count to be another value.
   if (1u == frame_count && all_data_received_ && !resume_decoding) {
     // Also use external allocator in situations when all of the data has been
     // received and there is not already a partial cache in the image decoder.
diff --git a/third_party/blink/renderer/platform/graphics/image_decoder_wrapper.h b/third_party/blink/renderer/platform/graphics/image_decoder_wrapper.h
index a4528b05..87400b2 100644
--- a/third_party/blink/renderer/platform/graphics/image_decoder_wrapper.h
+++ b/third_party/blink/renderer/platform/graphics/image_decoder_wrapper.h
@@ -31,7 +31,6 @@
 
   // Returns true if the decode succeeded.
   bool Decode(ImageDecoderFactory* factory,
-              wtf_size_t* frame_count,
               bool* has_alpha);
 
   // Indicates that the decode failed due to a corrupt image.
diff --git a/third_party/blink/renderer/platform/graphics/image_frame_generator.cc b/third_party/blink/renderer/platform/graphics/image_frame_generator.cc
index 1550b8e9..38a0a63 100644
--- a/third_party/blink/renderer/platform/graphics/image_frame_generator.cc
+++ b/third_party/blink/renderer/platform/graphics/image_frame_generator.cc
@@ -126,7 +126,6 @@
   const SkISize scaled_size = pixmap.dimensions();
   CHECK(GetSupportedDecodeSize(scaled_size) == scaled_size);
 
-  wtf_size_t frame_count = 0u;
   bool has_alpha = true;
 
   // |decode_failed| indicates a failure due to a corrupt image.
@@ -141,8 +140,8 @@
     ImageDecoderWrapper decoder_wrapper(this, data, pixmap,
                                         decoder_color_behavior_, aux_image_,
                                         index, all_data_received, client_id);
-    current_decode_succeeded = decoder_wrapper.Decode(
-        image_decoder_factory_.get(), &frame_count, &has_alpha);
+    current_decode_succeeded =
+        decoder_wrapper.Decode(image_decoder_factory_.get(), &has_alpha);
     decode_failed = decoder_wrapper.decode_failed();
   }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 12ec3214..04a1c1c 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -4273,6 +4273,11 @@
       base_feature: "none",
     },
     {
+      // If enabled, SharedWorker supports extendedLifetime
+      // https://github.com/whatwg/html/issues/10997
+      name: "SharedWorkerExtendedLifetime",
+    },
+    {
       name: "SignatureBasedInlineIntegrity",
       status: "experimental",
     },
diff --git a/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc b/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
index e01c284..b9c2a3e 100644
--- a/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
+++ b/third_party/blink/renderer/platform/video_capture/video_capture_impl.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/351564777): Remove this and convert code to safer constructs.
-#pragma allow_unsafe_buffers
-#endif
-
 // Notes about usage of this object by VideoCaptureImplManager.
 //
 // VideoCaptureImplManager access this object by using a Unretained()
@@ -23,12 +18,14 @@
 #include <memory>
 #include <utility>
 
+#include "base/containers/span.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/raw_span.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/task/bind_post_task.h"
@@ -121,8 +118,7 @@
   BufferContext& operator=(const BufferContext&) = delete;
 
   VideoFrameBufferHandleType buffer_type() const { return buffer_type_; }
-  const uint8_t* data() const { return data_; }
-  size_t data_size() const { return data_size_; }
+  base::span<const uint8_t> data() const { return data_; }
   const base::ReadOnlySharedMemoryRegion* read_only_shmem_region() const {
     return &read_only_shmem_region_;
   }
@@ -197,16 +193,14 @@
     DCHECK(region.IsValid());
     backup_mapping_ = region.Map();
     DCHECK(backup_mapping_.IsValid());
-    data_ = backup_mapping_.GetMemoryAsSpan<uint8_t>().data();
-    data_size_ = backup_mapping_.size();
+    data_ = backup_mapping_.GetMemoryAsSpan<uint8_t>();
   }
 
   void ResetPreMapping() {
     // If it's already mapped previously, then reset the mapping.
-    if (backup_mapping_.IsValid() || data_) {
+    if (backup_mapping_.IsValid() || data_.data()) {
       backup_mapping_ = base::WritableSharedMemoryMapping();
-      data_ = nullptr;
-      data_size_ = 0;
+      data_ = {};
     }
   }
 
@@ -216,8 +210,7 @@
     DCHECK(region.IsValid());
     read_only_mapping_ = region.Map();
     DCHECK(read_only_mapping_.IsValid());
-    data_ = read_only_mapping_.GetMemoryAsSpan<uint8_t>().data();
-    data_size_ = read_only_mapping_.size();
+    data_ = read_only_mapping_.GetMemoryAsSpan<uint8_t>();
     read_only_shmem_region_ = std::move(region);
   }
 
@@ -262,10 +255,9 @@
   // GMB comes premapped from the capturer.
   base::WritableSharedMemoryMapping backup_mapping_;
 
-  // These point into one of the above mappings, which hold the mapping open for
+  // This points into one of the above mappings, which hold the mapping open for
   // the lifetime of this object.
-  raw_ptr<const uint8_t> data_ = nullptr;
-  size_t data_size_ = 0;
+  base::raw_span<const uint8_t> data_;
 
   // Only valid for |buffer_type_ == SHARED_IMAGE_HANDLE|.
   scoped_refptr<gpu::ClientSharedImage> shared_image_;
@@ -312,9 +304,7 @@
             (media::VideoFrame::NumPlanes(
                  video_frame_init_data.ready_buffer->info->pixel_format) == 3))
             << "Currently, only YUV formats support custom strides.";
-        uint8_t* y_data = const_cast<uint8_t*>(buffer_context->data());
-        uint8_t* u_data =
-            y_data +
+        const size_t y_size =
             (media::VideoFrame::Rows(
                  media::VideoFrame::Plane::kY,
                  video_frame_init_data.ready_buffer->info->pixel_format,
@@ -322,8 +312,7 @@
                      .height()) *
              video_frame_init_data.ready_buffer->info->strides
                  ->stride_by_plane[0]);
-        uint8_t* v_data =
-            u_data +
+        const size_t u_size =
             (media::VideoFrame::Rows(
                  media::VideoFrame::Plane::kU,
                  video_frame_init_data.ready_buffer->info->pixel_format,
@@ -331,6 +320,9 @@
                      .height()) *
              video_frame_init_data.ready_buffer->info->strides
                  ->stride_by_plane[1]);
+        base::span<const uint8_t> data = buffer_context->data();
+        auto [y_data, uv_data] = data.split_at(y_size);
+        auto [u_data, v_data] = uv_data.split_at(u_size);
         video_frame_init_data.frame = media::VideoFrame::WrapExternalYuvData(
             video_frame_init_data.ready_buffer->info->pixel_format,
             gfx::Size(video_frame_init_data.ready_buffer->info->coded_size),
@@ -350,8 +342,7 @@
             gfx::Size(video_frame_init_data.ready_buffer->info->coded_size),
             gfx::Rect(video_frame_init_data.ready_buffer->info->visible_rect),
             video_frame_init_data.ready_buffer->info->visible_rect.size(),
-            const_cast<uint8_t*>(buffer_context->data()),
-            buffer_context->data_size(),
+            buffer_context->data(),
             video_frame_init_data.ready_buffer->info->timestamp);
       }
       break;
@@ -364,8 +355,7 @@
               gfx::Size(video_frame_init_data.ready_buffer->info->coded_size),
               gfx::Rect(video_frame_init_data.ready_buffer->info->visible_rect),
               video_frame_init_data.ready_buffer->info->visible_rect.size(),
-              const_cast<uint8_t*>(buffer_context->data()),
-              buffer_context->data_size(),
+              buffer_context->data(),
               video_frame_init_data.ready_buffer->info->timestamp);
       frame->BackWithSharedMemory(buffer_context->read_only_shmem_region());
       video_frame_init_data.frame = frame;
@@ -408,15 +398,15 @@
           mappable_buffers_not_supported_) {
         // The associated shared memory region is mapped only once.
         if (video_frame_init_data.ready_buffer->info->is_premapped &&
-            !buffer_context->data()) {
+            !buffer_context->data().data()) {
           auto gmb_handle = buffer_context->CloneGpuMemoryBufferHandle();
           buffer_context->InitializeFromUnsafeShmemRegion(
               std::move(gmb_handle).dxgi_handle().TakeRegion());
-          DCHECK(buffer_context->data());
+          DCHECK(buffer_context->data().data());
         }
         RequirePremappedFrames();
         if (!video_frame_init_data.ready_buffer->info->is_premapped ||
-            !buffer_context->data()) {
+            !buffer_context->data().data()) {
           // If the frame isn't premapped, can't do anything here.
           return false;
         }
@@ -428,8 +418,7 @@
                 gfx::Rect(
                     video_frame_init_data.ready_buffer->info->visible_rect),
                 video_frame_init_data.ready_buffer->info->visible_rect.size(),
-                const_cast<uint8_t*>(buffer_context->data()),
-                buffer_context->data_size(),
+                buffer_context->data(),
                 video_frame_init_data.ready_buffer->info->timestamp);
         if (!frame) {
           return false;
diff --git a/third_party/blink/renderer/platform/wtf/text/parsing_utilities.h b/third_party/blink/renderer/platform/wtf/text/parsing_utilities.h
index f900d20..c066575 100644
--- a/third_party/blink/renderer/platform/wtf/text/parsing_utilities.h
+++ b/third_party/blink/renderer/platform/wtf/text/parsing_utilities.h
@@ -41,17 +41,6 @@
 namespace WTF {
 
 template <typename CharType>
-bool SkipExactly(const CharType*& position,
-                 const CharType* end,
-                 CharType delimiter) {
-  if (position < end && *position == delimiter) {
-    ++position;
-    return true;
-  }
-  return false;
-}
-
-template <typename CharType>
 bool SkipExactly(base::span<const CharType> chars,
                  CharType delimiter,
                  size_t& position) {
@@ -121,12 +110,6 @@
   return position;
 }
 
-template <typename CharType, bool characterPredicate(CharType)>
-void SkipWhile(const CharType*& position, const CharType* end) {
-  while (position < end && characterPredicate(*position))
-    ++position;
-}
-
 template <typename CharType, bool predicate(CharType)>
 [[nodiscard]] size_t SkipWhile(base::span<const CharType> chars,
                                size_t position) {
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index 4f4914b..7d181b5 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -1455,3 +1455,5 @@
 crbug.com/332938942 [ Mac ] editing/selection/mouse/mouse-selection-vertical-rl.html [ Slow ]
 
 crbug.com/407795725 http/tests/inspector-protocol/tracing/frame-postmessage.js [ Slow ]
+
+crbug.com/413283544 [ Linux ] external/wpt/css/selectors/focus-visible-003.html [ Slow ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index ee2920b..d58940f 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1566,8 +1566,6 @@
 crbug.com/552494 virtual/prefer_compositing_to_lcd_text/scrollbars/overflow-scrollbar-combinations.html [ Failure Pass ]
 
 crbug.com/305376 [ Mac ] external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-024.html [ Failure ]
-crbug.com/1134483 [ Mac ] external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ Failure ]
-crbug.com/1134483 [ Mac ] external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.tentative.html [ Failure ]
 
 crbug.com/745905 external/wpt/css/css-ui/text-overflow-021.html [ Failure ]
 crbug.com/745905 external/wpt/css/css-overflow/text-overflow-scroll-001.html [ Failure ]
@@ -2607,6 +2605,26 @@
 # An unexplained failure on headless shell, opened an infra bug.
 crbug.com/410400550 [ Win ] external/wpt/css/motion/animation/offset-path-interpolation-008.html [ Failure ]
 
+# css-values-5 request modifiers
+crbug.com/413411328 external/wpt/css/css-values/urls/cross-origin/url-image-crossorigin-anonymous.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/cross-origin/url-image-crossorigin-use-credentials.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/no-referrer-when-downgrade/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/no-referrer-when-downgrade/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/no-referrer/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/no-referrer/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/origin-when-cross-origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/origin-when-cross-origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/same-origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/same-origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/strict-origin-when-cross-origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/strict-origin-when-cross-origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/strict-origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/strict-origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/unsafe-url/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
+crbug.com/413411328 external/wpt/css/css-values/urls/referrer-policy/unsafe-url/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
+
 # ====== New tests from wpt-importer added here ======
 external/wpt/service-workers/cache-storage/cache-add.https.any.* [ Failure Pass ]
 crbug.com/413085779 external/wpt/html/semantics/interactive-elements/the-dialog-element/dialog-requestclose-4-crash.html [ Crash ]
@@ -2616,24 +2634,7 @@
 [ Mac ] external/wpt/clear-site-data/clear-cache-partitioning.tentative.https.html [ Failure Pass Timeout ]
 [ Linux ] external/wpt/clear-site-data/clear-cache-partitioning.tentative.https.html [ Failure Pass Timeout ]
 crbug.com/411459672 external/wpt/css/css-sizing/min-content-le-max-content.tentative.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/cross-origin/url-image-crossorigin-anonymous.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/cross-origin/url-image-crossorigin-use-credentials.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/no-referrer-when-downgrade/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/no-referrer-when-downgrade/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/no-referrer/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/no-referrer/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/origin-when-cross-origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/origin-when-cross-origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/same-origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/same-origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/strict-origin-when-cross-origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/strict-origin-when-cross-origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/strict-origin/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/strict-origin/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/unsafe-url/url-image-referrerpolicy-cross-origin.sub.html [ Failure ]
-crbug.com/411574366 external/wpt/css/css-values/urls/referrer-policy/unsafe-url/url-image-referrerpolicy-same-origin.sub.html [ Failure ]
+
 crbug.com/411723253 [ Win10.20h2 ] external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html [ Timeout ]
 crbug.com/411576891 [ Linux ] external/wpt/webdriver/tests/classic/minimize_window/minimize.py [ Failure ]
 crbug.com/411723253 [ Win10.20h2 ] virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-installOnDevice.https.html [ Timeout ]
@@ -8251,6 +8252,7 @@
 
 # line-clamp
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-abspos-001.tentative.html [ Failure ]
+crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.tentative.html [ Failure ]
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-001.html [ Failure ]
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-007.tentative.html [ Failure ]
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-008.tentative.html [ Failure ]
@@ -8264,10 +8266,12 @@
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-019.tentative.html [ Failure ]
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-020.tentative.html [ Failure ]
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/block-ellipsis-021.tentative.html [ Failure ]
+crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ Failure ]
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-051.html [ Failure ]
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-052.html [ Failure ]
 crbug.com/40336192 external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-053.html [ Failure ]
 # css-line-clamp-line-breaking-ellipsis
+crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.tentative.html [ Pass ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-001.html [ Pass ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-007.tentative.html [ Pass ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-008.tentative.html [ Pass ]
@@ -8281,13 +8285,12 @@
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-019.tentative.html [ Pass ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-020.tentative.html [ Pass ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/block-ellipsis-021.tentative.html [ Pass ]
+crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ Pass ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-051.html [ Pass ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-052.html [ Pass ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-053.html [ Pass ]
-crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.tentative.html [ Failure ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-024.html [ Failure ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-035.html [ Failure ]
-crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ Failure ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-001.tentative.html [ Failure ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-002.tentative.html [ Failure ]
 crbug.com/40336192 virtual/css-line-clamp-line-breaking-ellipsis/external/wpt/css/css-flexbox/alignment/flex-align-baseline-line-clamp-003.tentative.html [ Failure ]
@@ -8342,7 +8345,6 @@
 crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-004.tentative.html [ Failure ]
 crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-005.tentative.html [ Failure ]
 crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.tentative.html [ Failure ]
-crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-010.tentative.html [ Failure ]
 crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-011.tentative.html [ Failure ]
 crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-013.tentative.html [ Failure ]
 crbug.com/40336192 virtual/disable-css-line-clamp/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-014.tentative.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.tentative.html
index 44d11105..33d4a9a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-009.tentative.html
@@ -4,10 +4,10 @@
 <link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
 <link rel="match" href="reference/webkit-line-clamp-036-ref.html">
-<meta name="assert" content="text-overflow: ellipsis doesn't apply in a line-clamp: auto context">
+<meta name="assert" content="text-overflow: ellipsis does apply in a line-clamp: auto context">
 <style>
 .clamp {
-  width: 10ch;
+  width: 10.1ch;
   max-height: 80px;
   font: 16px / 32px monospace;
   background-color: yellow;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-010.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-010.tentative.html
index cb706bb..6b69dc0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-010.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/line-clamp-auto-010.tentative.html
@@ -4,10 +4,10 @@
 <link rel="author" title="Andreu Botella" href="mailto:abotella@igalia.com">
 <link rel="help" href="https://drafts.csswg.org/css-overflow-4/#line-clamp">
 <link rel="match" href="reference/webkit-line-clamp-037-ref.html">
-<meta name="assert" content="text-overflow: ellipsis doesn't apply in a line-clamp: auto context, even if it doesn't have a max-height">
+<meta name="assert" content="text-overflow: ellipsis does apply in a line-clamp: auto context, even if it doesn't have a max-height">
 <style>
 .clamp {
-  width: 150px;
+  width: 15.1ch;
   font: 16px / 32px monospace;
   background-color: yellow;
   padding: 4px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/reference/webkit-line-clamp-036-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/reference/webkit-line-clamp-036-ref.html
index 0de35e9..321cce96 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/reference/webkit-line-clamp-036-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/reference/webkit-line-clamp-036-ref.html
@@ -4,7 +4,7 @@
 .clamp {
   display: -webkit-box;
   -webkit-box-orient: vertical;
-  width: 10ch;
+  width: 10.1ch;
   font: 16px / 32px monospace;
   background-color: yellow;
   padding: 4px;
@@ -12,6 +12,6 @@
 }
 </style>
 <div class="clamp">
-  supercalifragilisticexpialidocious
   supercali…
+  …
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/reference/webkit-line-clamp-037-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/reference/webkit-line-clamp-037-ref.html
index 2927716d..c5050d5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/reference/webkit-line-clamp-037-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/reference/webkit-line-clamp-037-ref.html
@@ -4,7 +4,7 @@
 .clamp {
   display: -webkit-box;
   -webkit-box-orient: vertical;
-  width: 150px;
+  width: 15.1ch;
   font: 16px / 32px monospace;
   background-color: yellow;
   padding: 4px;
@@ -12,6 +12,6 @@
 }
 </style>
 <div class="clamp">
-  supercalifragilisticexpialidocious
-  supercalifragilisticexpialidocious
+  supercalifragi…
+  supercalifragi…
 </div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html
index b8d7b19..437a93a7 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-036.html
@@ -2,13 +2,13 @@
 <meta charset="utf-8">
 <link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
 <link rel="match" href="reference/webkit-line-clamp-036-ref.html">
-<meta name="assert" content="text-overflow: ellipsis shouldn't apply within a -webkit-line-clamp context.">
+<meta name="assert" content="text-overflow: ellipsis does apply within a -webkit-line-clamp context.">
 <style>
 .clamp {
   display: -webkit-box;
   -webkit-box-orient: vertical;
   -webkit-line-clamp: 2;
-  width: 10ch;
+  width: 10.1ch;
   font: 16px / 32px monospace;
   background-color: yellow;
   padding: 4px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-037.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-037.html
index 8601522..70c33f75 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-037.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/line-clamp/webkit-line-clamp-037.html
@@ -2,13 +2,13 @@
 <meta charset="utf-8">
 <link rel="help" href="https://drafts.csswg.org/css-overflow-3/#webkit-line-clamp">
 <link rel="match" href="reference/webkit-line-clamp-037-ref.html">
-<meta name="assert" content="text-overflow: ellipsis shouldn't apply within a -webkit-line-clamp context.">
+<meta name="assert" content="text-overflow: ellipsis should apply within a -webkit-line-clamp context.">
 <style>
 .clamp {
   display: -webkit-box;
   -webkit-box-orient: vertical;
   -webkit-line-clamp: 2;
-  width: 150px;
+  width: 15.1ch;
   font: 16px / 32px monospace;
   background-color: yellow;
   padding: 4px;
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/get_all_cookies.sub.https.html b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/get_all_cookies.sub.https.html
index e941671..70a4ff7 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/get_all_cookies.sub.https.html
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/get_all_cookies.sub.https.html
@@ -17,8 +17,9 @@
   document.cookie = "test5=5; SameSite=Strict";
   document.cookie = "test6=6; SameSite=None; Secure";
   document.cookie = "test7=7; SameSite=Lax";
+  document.cookie = "test8=8; Secure; Partitioned; SameSite=None";
   const cookies = await test_driver.get_all_cookies();
-  assert_equals(cookies.length, 8);
+  assert_equals(cookies.length, 9);
   let cookieMap = new Map();
   for (const cookie of cookies) {
     cookieMap.set(cookie["name"], cookie);
@@ -108,5 +109,15 @@
   assert_equals(cookieMap.get("test7")["httpOnly"], false);
   assert_equals(cookieMap.get("test7")["expiry"], undefined);
   assert_equals(cookieMap.get("test7")["sameSite"], "Lax");
+
+  // test8 [Partitioned]
+  assert_equals(cookieMap.get("test8")["name"], "test8");
+  assert_equals(cookieMap.get("test8")["value"], "8");
+  assert_equals(cookieMap.get("test8")["path"], "/infrastructure/testdriver");
+  assert_equals(cookieMap.get("test8")["domain"], "{{host}}");
+  assert_equals(cookieMap.get("test8")["secure"], true);
+  assert_equals(cookieMap.get("test8")["httpOnly"], false);
+  assert_equals(cookieMap.get("test8")["expiry"], undefined);
+  assert_equals(cookieMap.get("test8")["sameSite"], "None");
 }, "Get all HTTPS cookies");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/get_named_cookie.sub.https.html b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/get_named_cookie.sub.https.html
index 8e8f4433..32b3bed 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/get_named_cookie.sub.https.html
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/testdriver/get_named_cookie.sub.https.html
@@ -17,6 +17,7 @@
   document.cookie = "test5=5; SameSite=Strict";
   document.cookie = "test6=6; SameSite=None; Secure";
   document.cookie = "test7=7; SameSite=Lax";
+  document.cookie = "test8=8; Secure; Partitioned; SameSite=None";
 
   // test0
   let cookie = await test_driver.get_named_cookie("test0");
@@ -110,5 +111,17 @@
   assert_equals(cookie["httpOnly"], false);
   assert_equals(cookie["expiry"], undefined);
   assert_equals(cookie["sameSite"], "Lax");
+
+  // test8 [Partitioned]
+  cookie = await test_driver.get_named_cookie("test8");
+  assert_equals(cookie["name"], "test8");
+  assert_equals(cookie["value"], "8");
+  assert_equals(cookie["path"], "/infrastructure/testdriver");
+  assert_equals(cookie["domain"], "{{host}}");
+  assert_equals(cookie["secure"], true);
+  assert_equals(cookie["httpOnly"], false);
+  assert_equals(cookie["expiry"], undefined);
+  assert_equals(cookie["sameSite"], "None");
+
 }, "Get Named HTTPS cookie");
 </script>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle-expected.txt
index 2484746..06c05bb 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/frame-lifecycle-expected.txt
@@ -108,6 +108,7 @@
 			frame: string
 			layerId: number
 			nodeId: number
+			nodeName: string
 		}
 	}
 	cat: string
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/rendering-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/rendering-expected.txt
index e47eff4..7824391 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/rendering-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/tracing/rendering-expected.txt
@@ -175,6 +175,7 @@
 		data: {
 			frame: string
 			nodeId: number
+			nodeName: string
 		}
 	}
 	cat: string
@@ -246,6 +247,7 @@
 				{
 					depth: number
 					nodeId: number
+					nodeName: string
 				}
 			]
 		}
diff --git a/third_party/compiler-rt/src b/third_party/compiler-rt/src
index c9899c3..a679a8b 160000
--- a/third_party/compiler-rt/src
+++ b/third_party/compiler-rt/src
@@ -1 +1 @@
-Subproject commit c9899c3706743f47be2397731ed25e10afb32321
+Subproject commit a679a8b8e33c2ba291fcab33ab2c628aa72c7aea
diff --git a/third_party/dawn b/third_party/dawn
index 5c35c84..5e81513 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 5c35c84f1b68cd0614266d1ba605d23bd3165ea8
+Subproject commit 5e815131e2179fa7ab3ceca4e0ad8f09ad04ca37
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 1188290..4c5c7fd 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 11882909f79cbad52255b8cadac40a7e115f518c
+Subproject commit 4c5c7fd1931eaa285aa1ee7484a158529e32138a
diff --git a/third_party/libc++/src b/third_party/libc++/src
index bb79a34..917609c 160000
--- a/third_party/libc++/src
+++ b/third_party/libc++/src
@@ -1 +1 @@
-Subproject commit bb79a34585926c20974cd7191b730ac94133a992
+Subproject commit 917609c669e43edc850eeb192a342434a54e1dfd
diff --git a/third_party/perfetto b/third_party/perfetto
index 6540d29..6acaf60 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 6540d2901b623f1cdca4772e5fe6ef873f558b42
+Subproject commit 6acaf607621ce5a117dc1a9773f27aa0d4c77b5b
diff --git a/tools/clang/spanify/Spanifier.cpp b/tools/clang/spanify/Spanifier.cpp
index b951babf..b2f8a0a 100644
--- a/tools/clang/spanify/Spanifier.cpp
+++ b/tools/clang/spanify/Spanifier.cpp
@@ -284,7 +284,7 @@
 // - include-system-header:::<file path>:::-1:::-1:::<include text>
 //
 // It is associated with a "Node", which is a unique identifier.
-void EmitReplacement(const std::string& node, const std::string& replacement) {
+void EmitReplacement(std::string_view node, std::string_view replacement) {
   Emit(llvm::formatv("r {0} {1}\n", node, replacement));
 }
 
@@ -905,6 +905,44 @@
       GetReplacementDirective(rep_range, replacement_text, source_manager));
 }
 
+// Given that we want to emit `.subspan(expr)`,
+// *  if `expr` is observably unsigned, does nothing.
+// *  if `expr` is a signed int literal, appends `u`.
+// *  otherwise, wraps `expr` with `checked_cast`.
+void RewriteExprForSubspan(const clang::Expr* expr,
+                           const MatchFinder::MatchResult& result,
+                           std::string_view key) {
+  clang::QualType type = expr->getType();
+  const clang::ASTContext& ast_context = *result.Context;
+
+  // This logic isn't perfect: an unsigned type wider than `size_t`
+  // will pop us out of this function, but will fail the `strict_cast`
+  // imposed by `subspan()`.
+  if (type == ast_context.getCorrespondingUnsignedType(type)) {
+    return;
+  }
+
+  const clang::SourceManager& source_manager = *result.SourceManager;
+  const clang::SourceRange range =
+      getExprRange(expr, source_manager, result.Context->getLangOpts());
+
+  if (clang::dyn_cast<clang::IntegerLiteral>(expr)) {
+    EmitReplacement(
+        key, GetReplacementDirective(range.getEnd(), "u", source_manager));
+    return;
+  }
+
+  EmitReplacement(key, GetReplacementDirective(range.getBegin(),
+                                               "base::checked_cast<size_t>(",
+                                               source_manager));
+  EmitReplacement(key,
+                  GetReplacementDirective(range.getEnd(), ")", source_manager));
+  EmitReplacement(key, GetIncludeDirective(range, source_manager,
+                                           "base/numerics/safe_conversions.h"));
+  EmitReplacement(key, GetIncludeDirective(range, source_manager, "cstdint",
+                                           /*is_system_include_path=*/true));
+}
+
 // Handle the case where we match `&container[<offset>]` being used as a buffer.
 void EmitContainerPointerRewrites(const MatchFinder::MatchResult& result,
                                   const std::string& key) {
@@ -962,6 +1000,10 @@
       replacement_range = {
           c_style_array_with_subscript.getEndLoc(),
           c_style_array_with_subscript.getEndLoc().getLocWithOffset(1)};
+      const auto* subscript = GetNodeOrCrash<clang::Expr>(
+          result, "c_style_array_subscript",
+          "expected when `container_subscript` is not bound");
+      RewriteExprForSubspan(subscript, result, key);
     }
     // Close the call to `.subspan()`.
     replacement_text = ")";
@@ -2199,7 +2241,7 @@
                         declRefExpr(to(varDecl(hasType(arrayType(hasElementType(
                                         qualType().bind("contained_type")))))))
                             .bind("container_decl_ref")),
-                    hasIndex(expr()),
+                    hasIndex(expr().bind("c_style_array_subscript")),
                     optionally(hasIndex(integerLiteral(equals(0u))
                                             .bind("zero_container_offset"))))
                     .bind("c_style_array_with_subscript"))))
diff --git a/tools/clang/spanify/tests/pointer-into-c-style-array-issue-402806166-expected.cc b/tools/clang/spanify/tests/pointer-into-c-style-array-issue-402806166-expected.cc
index f3dda62..f3fe29f 100644
--- a/tools/clang/spanify/tests/pointer-into-c-style-array-issue-402806166-expected.cc
+++ b/tools/clang/spanify/tests/pointer-into-c-style-array-issue-402806166-expected.cc
@@ -27,6 +27,6 @@
   // With an offset (subspan) in play, we need to also wrap the array
   // declref in a `base::span()`.
   // Expected rewrite:
-  // UseBufferUnsafely(base::span<int>(array).subspan(1));
-  UseBufferUnsafely(base::span<int>(array).subspan(1));
+  // UseBufferUnsafely(base::span<int>(array).subspan(1u));
+  UseBufferUnsafely(base::span<int>(array).subspan(1u));
 }
diff --git a/tools/clang/spanify/tests/pointer-into-c-style-array-issue-402806166-original.cc b/tools/clang/spanify/tests/pointer-into-c-style-array-issue-402806166-original.cc
index a3646c1e7..a2a981f 100644
--- a/tools/clang/spanify/tests/pointer-into-c-style-array-issue-402806166-original.cc
+++ b/tools/clang/spanify/tests/pointer-into-c-style-array-issue-402806166-original.cc
@@ -25,6 +25,6 @@
   // With an offset (subspan) in play, we need to also wrap the array
   // declref in a `base::span()`.
   // Expected rewrite:
-  // UseBufferUnsafely(base::span<int>(array).subspan(1));
+  // UseBufferUnsafely(base::span<int>(array).subspan(1u));
   UseBufferUnsafely(&array[1]);
 }
diff --git a/tools/clang/spanify/tests/subspan-arg-must-be-unsigned-issue-412395846-expected.cc b/tools/clang/spanify/tests/subspan-arg-must-be-unsigned-issue-412395846-expected.cc
new file mode 100644
index 0000000..37078262
--- /dev/null
+++ b/tools/clang/spanify/tests/subspan-arg-must-be-unsigned-issue-412395846-expected.cc
@@ -0,0 +1,69 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <array>
+#include <cstdint>
+
+#include "base/containers/span.h"
+#include "base/numerics/safe_conversions.h"
+
+int UnsafeIndex();
+unsigned int UnsafeUnsignedIndex();
+
+// Expected rewrite:
+// void UnsafelyModifyIntSlice(base::span<int> slice) {
+void UnsafelyModifyIntSlice(base::span<int> slice) {
+  slice[UnsafeIndex()] = 0;
+}
+
+void SubspanIntoCStyleArrayIsUnsigned() {
+  // Expected rewrite:
+  // static auto ints = std::to_array<int>({0, 1, 2});
+  static auto ints = std::to_array<int>({0, 1, 2});
+
+  // Unsigned integer literal -> propagate it as written.
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(base::span<int>(ints).subspan(1u));
+  UnsafelyModifyIntSlice(base::span<int>(ints).subspan(1u));
+
+  // Expression that evaluates unsigned -> propagate it as written.
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(base::span<int>(ints).subspan(UnsafeUnsignedIndex()));
+  UnsafelyModifyIntSlice(base::span<int>(ints).subspan(UnsafeUnsignedIndex()));
+
+  // Signed integer literal -> postfix `u`.
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(base::span<int>(ints).subspan(2u));
+  UnsafelyModifyIntSlice(base::span<int>(ints).subspan(2u));
+
+  // 1. Different signedness
+  // 2. `3` is promoted to unsigned
+  // 3. We can see that the expression is unsigned
+  // 4. Propagate it as written.
+  //
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(base::span<int>(ints).subspan(2u - 1));
+  UnsafelyModifyIntSlice(base::span<int>(ints).subspan(2u - 1));
+
+  // Indexing expression is not integer literal and is signed
+  // -> wrap the expression in `checked_cast`.
+  //
+  // Incidentally, this triggers arrayification of `ints`.
+  //
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(
+  //     base::span<int>(ints).subspan(base::checked_cast<size_t>(UnsafeIndex())));
+  UnsafelyModifyIntSlice(
+      base::span<int>(ints).subspan(base::checked_cast<size_t>(UnsafeIndex())));
+
+  // Compound version of the preceding
+  // -> wrap the expression in `checked_cast`.
+  //
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(
+  //     base::span<int>(ints).subspan(base::checked_cast<size_t>(UnsafeIndex()
+  //     + 1)));
+  UnsafelyModifyIntSlice(base::span<int>(ints).subspan(
+      base::checked_cast<size_t>(UnsafeIndex() + 1)));
+}
diff --git a/tools/clang/spanify/tests/subspan-arg-must-be-unsigned-issue-412395846-original.cc b/tools/clang/spanify/tests/subspan-arg-must-be-unsigned-issue-412395846-original.cc
new file mode 100644
index 0000000..083b078
--- /dev/null
+++ b/tools/clang/spanify/tests/subspan-arg-must-be-unsigned-issue-412395846-original.cc
@@ -0,0 +1,61 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+int UnsafeIndex();
+unsigned int UnsafeUnsignedIndex();
+
+// Expected rewrite:
+// void UnsafelyModifyIntSlice(base::span<int> slice) {
+void UnsafelyModifyIntSlice(int* slice) {
+  slice[UnsafeIndex()] = 0;
+}
+
+void SubspanIntoCStyleArrayIsUnsigned() {
+  // Expected rewrite:
+  // static auto ints = std::to_array<int>({0, 1, 2});
+  static int ints[] = {0, 1, 2};
+
+  // Unsigned integer literal -> propagate it as written.
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(base::span<int>(ints).subspan(1u));
+  UnsafelyModifyIntSlice(&ints[1u]);
+
+  // Expression that evaluates unsigned -> propagate it as written.
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(base::span<int>(ints).subspan(UnsafeUnsignedIndex()));
+  UnsafelyModifyIntSlice(&ints[UnsafeUnsignedIndex()]);
+
+  // Signed integer literal -> postfix `u`.
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(base::span<int>(ints).subspan(2u));
+  UnsafelyModifyIntSlice(&ints[2]);
+
+  // 1. Different signedness
+  // 2. `3` is promoted to unsigned
+  // 3. We can see that the expression is unsigned
+  // 4. Propagate it as written.
+  //
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(base::span<int>(ints).subspan(2u - 1));
+  UnsafelyModifyIntSlice(&ints[2u - 1]);
+
+  // Indexing expression is not integer literal and is signed
+  // -> wrap the expression in `checked_cast`.
+  //
+  // Incidentally, this triggers arrayification of `ints`.
+  //
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(
+  //     base::span<int>(ints).subspan(base::checked_cast<size_t>(UnsafeIndex())));
+  UnsafelyModifyIntSlice(&ints[UnsafeIndex()]);
+
+  // Compound version of the preceding
+  // -> wrap the expression in `checked_cast`.
+  //
+  // Expected rewrite:
+  // UnsafelyModifyIntSlice(
+  //     base::span<int>(ints).subspan(base::checked_cast<size_t>(UnsafeIndex()
+  //     + 1)));
+  UnsafelyModifyIntSlice(&ints[UnsafeIndex() + 1]);
+}
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index fa3374b72..c66a8437 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -14133,6 +14133,7 @@
   <int value="-75418012" label="ContextualSuggestionsOptOut:disabled"/>
   <int value="-75396730" label="UseAlternateHistorySyncIllustration:disabled"/>
   <int value="-74964571" label="EcheCustomWidget:enabled"/>
+  <int value="-74808314" label="WebRtcWgcRequireBorder:enabled"/>
   <int value="-73282711" label="EnableAppListSearchAutocomplete:enabled"/>
   <int value="-72904030" label="ChromeRefresh2023:enabled"/>
   <int value="-72691146" label="SafetyHubFollowup:disabled"/>
@@ -18859,6 +18860,7 @@
       label="AccessibilityDictationKeyboardImprovements:disabled"/>
   <int value="1726578007" label="EnableFileBackedBlobFactory:disabled"/>
   <int value="1727201171" label="EventBasedLogUpload:enabled"/>
+  <int value="1728616693" label="WebRtcWgcRequireBorder:disabled"/>
   <int value="1728816382" label="SeaPenEnterprise:disabled"/>
   <int value="1729485335" label="CastMessageLogging:disabled"/>
   <int value="1730094138" label="enable-md-storage-manager"/>
diff --git a/tools/metrics/histograms/metadata/autofill/enums.xml b/tools/metrics/histograms/metadata/autofill/enums.xml
index 096a964..52d62197 100644
--- a/tools/metrics/histograms/metadata/autofill/enums.xml
+++ b/tools/metrics/histograms/metadata/autofill/enums.xml
@@ -143,6 +143,13 @@
   <int value="2" label="Import with section union"/>
 </enum>
 
+<enum name="AutofillAiOptInFunnelEvents">
+  <int value="0" label="IPH was shown"/>
+  <int value="1" label="FFR was shown"/>
+  <int value="2" label="The FFR learn more button was clicked"/>
+  <int value="3" label="FFR was accepted"/>
+</enum>
+
 <enum name="AutofillAlternativeNameFieldValueCharacterSet">
   <int value="0" label="Katakana"/>
   <int value="1" label="Hiragana"/>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index 5e11f4c9e..a06f9a5 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -1076,6 +1076,18 @@
   </token>
 </histogram>
 
+<histogram name="Autofill.Ai.OptInFunnel" enum="AutofillAiOptInFunnelEvents"
+    expires_after="2025-12-12">
+  <owner>brunobraga@google.com</owner>
+  <owner>chrome-autofill-alerts@google.com</owner>
+  <summary>
+    Records events related to the surfaces available for users to find out and
+    opt in Autofill AI. These educational surfaces are the IPH and the FFR
+    dialog. Logged when any of them are shown and when the FFR (final step in
+    the funnel) is accepted.
+  </summary>
+</histogram>
+
 <histogram
     name="Autofill.AmountExtraction.HeuristicRegexesComponentInstallationResult"
     enum="AmountExtractionComponentInstallationResult"
@@ -4339,6 +4351,70 @@
   <token key="OtpAuthType" variants="Autofill.OtpAuth.Type"/>
 </histogram>
 
+<histogram
+    name="Autofill.OtpInputDialog.{CardType}.{OtpAuthType}.ErrorMessageShown"
+    enum="AutofillOtpInputDialogError" expires_after="2025-07-01">
+  <owner>siyua@chromium.org</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    Logs the type of the error when any temporary error is shown in the card
+    unmask OTP dialog.
+  </summary>
+  <token key="CardType" variants="Autofill.PaymentsRpcCardType"/>
+  <token key="OtpAuthType" variants="Autofill.OtpAuth.Type"/>
+</histogram>
+
+<histogram
+    name="Autofill.OtpInputDialog.{CardType}.{OtpAuthType}.NewOtpRequested"
+    enum="BooleanRequested" expires_after="2025-07-01">
+  <owner>siyua@chromium.org</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    Emits true when the &quot;Get New Code&quot; button is clicked and a new OTP
+    is requested. Doesn't emit any other value.
+  </summary>
+  <token key="CardType" variants="Autofill.PaymentsRpcCardType"/>
+  <token key="OtpAuthType" variants="Autofill.OtpAuth.Type"/>
+</histogram>
+
+<histogram name="Autofill.OtpInputDialog.{CardType}.{OtpAuthType}.Result"
+    enum="AutofillOtpInputDialogResult" expires_after="2025-08-31">
+  <owner>siyua@chromium.org</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    Logs the reason of the closure when the card unmask OTP dialog is closed.
+  </summary>
+  <token key="CardType" variants="Autofill.PaymentsRpcCardType"/>
+  <token key="OtpAuthType" variants="Autofill.OtpAuth.Type"/>
+</histogram>
+
+<histogram
+    name="Autofill.OtpInputDialog.{CardType}.{OtpAuthType}.Result.{WithError}"
+    enum="AutofillOtpInputDialogResult" expires_after="2025-07-01">
+  <owner>siyua@chromium.org</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    Logs the reason of the closure when the card unmask OTP dialog is closed.
+    This breaks down on whether any temporary error happened before the card
+    unmask OTP dialog was closed.
+  </summary>
+  <token key="CardType" variants="Autofill.PaymentsRpcCardType"/>
+  <token key="OtpAuthType" variants="Autofill.OtpAuth.Type"/>
+  <token key="WithError" variants="Autofill.DialogError"/>
+</histogram>
+
+<histogram name="Autofill.OtpInputDialog.{CardType}.{OtpAuthType}.Shown"
+    enum="BooleanShown" expires_after="2025-08-31">
+  <owner>siyua@chromium.org</owner>
+  <owner>payments-autofill-team@google.com</owner>
+  <summary>
+    Emits true when the card unmask OTP dialog is shown. Doesn't emit any other
+    value.
+  </summary>
+  <token key="CardType" variants="Autofill.PaymentsRpcCardType"/>
+  <token key="OtpAuthType" variants="Autofill.OtpAuth.Type"/>
+</histogram>
+
 <histogram name="Autofill.OtpInputDialog.{OtpAuthType}.ErrorMessageShown"
     enum="AutofillOtpInputDialogError" expires_after="2025-07-01">
   <owner>siyua@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/enums.xml b/tools/metrics/histograms/metadata/blink/enums.xml
index 23de9fa..38621c83 100644
--- a/tools/metrics/histograms/metadata/blink/enums.xml
+++ b/tools/metrics/histograms/metadata/blink/enums.xml
@@ -6174,6 +6174,8 @@
   <int value="5547" label="InputParsedAncestorSelect"/>
   <int value="5548" label="CSSSelectorPseudoHasSlotted"/>
   <int value="5549" label="SelectMultipleShowPopup"/>
+  <int value="5550" label="SharedWorkerExtendedLifetimeFeatureEnabled"/>
+  <int value="5551" label="SharedWorkerExtendedLifetimeIsTrue"/>
 </enum>
 
 <!-- LINT.ThenChange(//third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom:WebFeature) -->
@@ -8175,6 +8177,8 @@
   <int value="318" label="TextBox"/>
   <int value="319" label="ScrollInitialTarget"/>
   <int value="320" label="HasSlotted"/>
+  <int value="321" label="ElementCapture"/>
+  <int value="322" label="RegionCapture"/>
 </enum>
 
 <!-- LINT.ThenChange(//third_party/blink/public/mojom/use_counter/metrics/webdx_feature.mojom:WebDXFeature) -->
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index b1c0033..f682d2b3 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -966,6 +966,27 @@
   </summary>
 </histogram>
 
+<histogram name="Media.Audio.Mac.AggregateDeviceIsPrivate{Type}" enum="Boolean"
+    expires_after="2025-10-10">
+  <owner>kron@chromium.org</owner>
+  <owner>webrtc-audio-uma@google.com</owner>
+  <summary>
+    Reports the number of occurences of private aggregate devices where the
+    dictionary entry for kAudioAggregateDeviceIsPrivateKey is successfully
+    interpreted as a {Type}. Recorded for each call to
+    IsPrivateAggregateDevice() if the AudioObjectID is of type
+    kAudioDeviceTransportTypeAggregate and there is a dictionary. This histogram
+    is used to determine the type of the value in the dictionary for
+    kAudioAggregateDeviceIsPrivateKey. If no occurences of type Number are
+    detected, we can remove the code path that parses the dictionary as a
+    number.
+  </summary>
+  <token key="Type">
+    <variant name="Boolean"/>
+    <variant name="Number"/>
+  </token>
+</histogram>
+
 <histogram name="Media.Audio.Mac.HardwareLatency.{Direction}.{Component}"
     units="misses" expires_after="2025-09-14">
   <owner>tguilbert@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/settings/enums.xml b/tools/metrics/histograms/metadata/settings/enums.xml
index 837f16b..3c1e4a5 100644
--- a/tools/metrics/histograms/metadata/settings/enums.xml
+++ b/tools/metrics/histograms/metadata/settings/enums.xml
@@ -137,6 +137,7 @@
   <int value="8" label="Notification not revoked because of default block"/>
   <int value="9" label="Previously marked as false positive"/>
   <int value="10" label="Revoke"/>
+  <int value="11" label="Ignore"/>
 </enum>
 
 <!-- LINT.ThenChange(//chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.h:RevocationResult) -->
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 585961a..acd91cc7 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v50.1/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "fa53ec450ba354d04e1c56c06bc8e45fb5ac450d",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/6540d2901b623f1cdca4772e5fe6ef873f558b42/trace_processor_shell.exe"
+            "hash": "cb116261436227ccbb63f5179aab563c8fb6cba7",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/8a38f2ad11b6b2bf0a4d60b39c79d00d35fdbc01/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "99f971ca131f6d11c73f4b918099d434bdd8093c",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v50.1/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "3b0796b538027bd37ba26668ca3ba178b7b3abc1",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/6540d2901b623f1cdca4772e5fe6ef873f558b42/trace_processor_shell"
+            "hash": "c5095d45c469e7f629d445dfff678d2a1d5811c8",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/6acaf607621ce5a117dc1a9773f27aa0d4c77b5b/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 59f037b..cf6d860 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -510,4 +510,5 @@
  <item id="baguette_image_download" added_in_milestone="136" content_hash_code="0416bbbe" os_list="chromeos" file_path="chrome/browser/ash/crostini/baguette_download.cc" />
  <item id="chromeos_walrus_provider" added_in_milestone="136" content_hash_code="0671ae94" os_list="chromeos" file_path="components/manta/walrus_provider.cc" />
  <item id="autofill_image_fetcher" added_in_milestone="137" content_hash_code="019e07fc" os_list="linux,windows,chromeos" file_path="components/autofill/core/browser/ui/autofill_image_fetcher.cc" />
+ <item id="forms_classifications_model_execution" added_in_milestone="137" content_hash_code="051d9c71" os_list="linux,windows,chromeos,android" file_path="components/optimization_guide/core/model_execution/model_execution_fetcher.cc" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index b58ae488..dfdf809 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -375,6 +375,7 @@
       <annotation id="chromeos_inline_image_request"/>
       <annotation id="baguette_image_download"/>
       <annotation id="chromeos_walrus_provider"/>
+      <annotation id="forms_classifications_model_execution"/>
     </sender>
   </group>
   <group name="Admin Features" hidden="true">
diff --git a/ui/android/java/src/org/chromium/ui/base/EventForwarder.java b/ui/android/java/src/org/chromium/ui/base/EventForwarder.java
index 0fb9055..95c271e5 100644
--- a/ui/android/java/src/org/chromium/ui/base/EventForwarder.java
+++ b/ui/android/java/src/org/chromium/ui/base/EventForwarder.java
@@ -711,7 +711,9 @@
         // and ACTION_BUTTON_RELEASE respectively because they provide
         // info about the changed-button.
         if (event.getAction() == MotionEvent.ACTION_DOWN
-                || event.getAction() == MotionEvent.ACTION_UP) {
+                || event.getAction() == MotionEvent.ACTION_UP
+                || event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
+                || event.getActionMasked() == MotionEvent.ACTION_POINTER_UP) {
             // While we use the action buttons for the changed state it is important to still
             // consume the down/up events to get the complete stream for a drag gesture, which
             // is provided using ACTION_MOVE touch events.
@@ -765,9 +767,10 @@
         float offsetX = 0.0f;
         float offsetY = 0.0f;
 
-        // TODO(crbug.com/405067297): support multi-finger events on the trackpad (scroll, right
-        // click & middle click)
+        // TODO(crbug.com/405067297): support multi-finger events on the trackpad (scroll)
         if (event.isFromSource(InputDevice.SOURCE_TOUCHPAD)) {
+            event = updateTrackpadButtonState(event);
+
             // Ignore calculating the offset if we don't have the previous event trackpad position
             if (mIsLastTrackpadPositionValid) {
                 // Input device is trackpad, getX & getY return the raw finger position on the
@@ -780,7 +783,17 @@
 
             mLastTrackpadPositionX = event.getX();
             mLastTrackpadPositionY = event.getY();
-            mIsLastTrackpadPositionValid = (event.getAction() != MotionEvent.ACTION_UP);
+
+            // Invalidate the trackpad position for these cases:
+            // ACTION_UP: No pointer on the trackpad, the position data is stale
+            // ACTION_POINTER_UP & ACTION_POINTER_DOWN: Multiple trackpad pointers causes the main
+            // pointer (pointer at idx 0) to flip from one to another, causing sudden jumps in
+            // offsets, we need to wait for a subsequent event to figure out the correct trackpad
+            // position
+            mIsLastTrackpadPositionValid =
+                    (event.getAction() != MotionEvent.ACTION_UP
+                            && event.getActionMasked() != MotionEvent.ACTION_POINTER_UP
+                            && event.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN);
         } else if (event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) {
             // Input device is Mouse, getX & getY return the relative change of the pointer position
             offsetX = event.getX();
@@ -800,6 +813,65 @@
         return ret;
     }
 
+    private static MotionEvent updateTrackpadButtonState(MotionEvent event) {
+        if (event.getAction() != MotionEvent.ACTION_BUTTON_PRESS
+                && event.getAction() != MotionEvent.ACTION_BUTTON_RELEASE) {
+            return event;
+        }
+
+        // When the pointer is captured, all clicks on trackpad would have a BUTTON_PRIMARY state,
+        // regardless of how many pointers are on the trackpad. So, we need to correct the button
+        // state
+        int updatedButtonState;
+        if (event.getPointerCount() == 2) {
+            updatedButtonState = MotionEvent.BUTTON_SECONDARY;
+        } else if (event.getPointerCount() == 3) {
+            updatedButtonState = MotionEvent.BUTTON_TERTIARY;
+        } else {
+            // No change is made on the event
+            return event;
+        }
+
+        return MotionEvent.obtain(
+                event.getDownTime(),
+                event.getEventTime(),
+                event.getAction(),
+                event.getPointerCount(),
+                getPointerPropertiesForEvent(event),
+                getPointerCoordsForEvent(event),
+                event.getMetaState(),
+                updatedButtonState,
+                event.getXPrecision(),
+                event.getYPrecision(),
+                event.getDeviceId(),
+                event.getEdgeFlags(),
+                event.getSource(),
+                event.getFlags());
+    }
+
+    private static MotionEvent.PointerProperties[] getPointerPropertiesForEvent(MotionEvent event) {
+        MotionEvent.PointerProperties[] ret =
+                new MotionEvent.PointerProperties[event.getPointerCount()];
+        for (int i = 0; i < event.getPointerCount(); i++) {
+            MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
+            event.getPointerProperties(i, properties);
+
+            ret[i] = properties;
+        }
+        return ret;
+    }
+
+    private static MotionEvent.PointerCoords[] getPointerCoordsForEvent(MotionEvent event) {
+        MotionEvent.PointerCoords[] ret = new MotionEvent.PointerCoords[event.getPointerCount()];
+        for (int i = 0; i < event.getPointerCount(); i++) {
+            MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+            event.getPointerCoords(i, coords);
+
+            ret[i] = coords;
+        }
+        return ret;
+    }
+
     /**
      * @see View#onKeyUp()
      */
diff --git a/ui/android/junit/src/org/chromium/ui/base/EventForwarderTest.java b/ui/android/junit/src/org/chromium/ui/base/EventForwarderTest.java
index 57bf8d2..ba05025 100644
--- a/ui/android/junit/src/org/chromium/ui/base/EventForwarderTest.java
+++ b/ui/android/junit/src/org/chromium/ui/base/EventForwarderTest.java
@@ -365,6 +365,55 @@
     }
 
     @Test
+    public void testCapturedPointerTrackpadRightClickEvent() {
+        testCapturedPointerTrackpadMultiTouchClickEvent(2, MotionEvent.BUTTON_SECONDARY);
+    }
+
+    @Test
+    public void testCapturedPointerTrackpadMiddleClickEvent() {
+        testCapturedPointerTrackpadMultiTouchClickEvent(3, MotionEvent.BUTTON_TERTIARY);
+    }
+
+    private void testCapturedPointerTrackpadMultiTouchClickEvent(int pointersCnt, int buttonState) {
+        EventForwarder eventForwarder = new EventForwarder(NATIVE_EVENT_FORWARDER_ID, true, true);
+
+        MotionEvent moveEvent =
+                MotionEvent.obtain(
+                        0,
+                        0,
+                        MotionEvent.ACTION_BUTTON_PRESS,
+                        pointersCnt,
+                        getToolTypeFingerProperties(pointersCnt),
+                        getPointerCoords(pointersCnt),
+                        0,
+                        MotionEvent.BUTTON_PRIMARY,
+                        0,
+                        0,
+                        0,
+                        0,
+                        InputDevice.SOURCE_TOUCHPAD,
+                        0);
+
+        eventForwarder.onCapturedPointerEvent(moveEvent);
+        verify(mNativeMock, times(1))
+                .onMouseEvent(
+                        NATIVE_EVENT_FORWARDER_ID,
+                        eventForwarder,
+                        MotionEventUtils.getEventTimeNanos(moveEvent),
+                        moveEvent.getActionMasked(),
+                        0,
+                        0,
+                        moveEvent.getPointerId(0),
+                        moveEvent.getPressure(0),
+                        moveEvent.getOrientation(0),
+                        moveEvent.getAxisValue(MotionEvent.AXIS_TILT, 0),
+                        EventForwarder.getMouseEventActionButton(moveEvent),
+                        buttonState,
+                        moveEvent.getMetaState(),
+                        MotionEvent.TOOL_TYPE_MOUSE);
+    }
+
+    @Test
     public void testCapturedPointerMouseMoveEvent() {
         EventForwarder eventForwarder = new EventForwarder(NATIVE_EVENT_FORWARDER_ID, true, true);
 
@@ -484,8 +533,8 @@
                 0,
                 action,
                 1,
-                getToolTypeFingerProperties(),
-                getPointerCoords(),
+                getToolTypeFingerProperties(1),
+                getPointerCoords(1),
                 0,
                 buttonState,
                 0,
@@ -496,22 +545,26 @@
                 0);
     }
 
-    private static MotionEvent.PointerProperties[] getToolTypeFingerProperties() {
+    private static MotionEvent.PointerProperties[] getToolTypeFingerProperties(int pointersCnt) {
         MotionEvent.PointerProperties[] pointerPropertiesArray =
-                new MotionEvent.PointerProperties[1];
-        MotionEvent.PointerProperties trackpadProperties = new MotionEvent.PointerProperties();
-        trackpadProperties.id = 7;
-        trackpadProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
-        pointerPropertiesArray[0] = trackpadProperties;
+                new MotionEvent.PointerProperties[pointersCnt];
+        for (int i = 0; i < pointersCnt; i++) {
+            MotionEvent.PointerProperties trackpadProperties = new MotionEvent.PointerProperties();
+            trackpadProperties.id = 7 + i;
+            trackpadProperties.toolType = MotionEvent.TOOL_TYPE_FINGER;
+            pointerPropertiesArray[i] = trackpadProperties;
+        }
         return pointerPropertiesArray;
     }
 
-    private static MotionEvent.PointerCoords[] getPointerCoords() {
-        MotionEvent.PointerCoords[] pointerCoordsArray = new MotionEvent.PointerCoords[1];
-        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
-        coords.x = 14;
-        coords.y = 21;
-        pointerCoordsArray[0] = coords;
+    private static MotionEvent.PointerCoords[] getPointerCoords(int pointersCnt) {
+        MotionEvent.PointerCoords[] pointerCoordsArray = new MotionEvent.PointerCoords[pointersCnt];
+        for (int i = 0; i < pointersCnt; i++) {
+            MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+            coords.x = 14 + i;
+            coords.y = 21 + i;
+            pointerCoordsArray[i] = coords;
+        }
         return pointerCoordsArray;
     }
 
diff --git a/ui/base/models/list_selection_model.h b/ui/base/models/list_selection_model.h
index 8216b15..926e75dd 100644
--- a/ui/base/models/list_selection_model.h
+++ b/ui/base/models/list_selection_model.h
@@ -90,8 +90,9 @@
   // this sets the anchor, selection and active indices to |index|.
   void SetSelectionFromAnchorTo(size_t index);
 
-  // Makes sure the indices from the anchor to |index| are selected. This only
-  // adds to the selection.
+  // Makes sure the tabs from the anchor to |index| are selected. This adds to
+  // the selection if there is an anchor and resets the selection to |index| if
+  // there is not an anchor.
   void AddSelectionFromAnchorTo(size_t index);
 
   // Invoked when an item moves. |old_index| is the original index, |new_index|
diff --git a/v8 b/v8
index fffec1a..299a02b 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit fffec1a2cbac6191832d0ad4e27eb35685a2669f
+Subproject commit 299a02b2c82bfd6bffe69b1675d154190290bbcd