diff --git a/DEPS b/DEPS
index e0f2ca9..daef8a8 100644
--- a/DEPS
+++ b/DEPS
@@ -309,15 +309,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'bcd22e8f95bc68ef8c160cf49d335befc3b63366',
+  'skia_revision': '8e9e168418a01ef4d7e4b2db81971a8c8d59dae9',
   # 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': '0820381ccfe67b31f2f65548450d5e1a15a6cffe',
+  'v8_revision': 'c53da0241d47fd6684a2865df5102929ba87eee8',
   # 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': '8ae36a93bedcb867cb8663c72751bed9c88af68b',
+  'angle_revision': '6d50433c432b59a77773ccd2b757f09858a51093',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -384,7 +384,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling chromium_variations
   # and whatever else without interference from each other.
-  'chromium_variations_revision': '61c4f0ba1165b4df1b69408e47a5c3b096f5af50',
+  'chromium_variations_revision': '84e8cf17ab35bc49df8fcad65854f24f5472418b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -400,7 +400,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': 'c5a9286c173131ad93ef12abe79202cd59dae86b',
+  'devtools_frontend_revision': 'd13f23b6b05b66be191294a0be6832b1d5f66a3a',
   # 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.
@@ -468,7 +468,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.
-  'cros_components_revision': '8d31f9f910806d357fa2031e152c4b415a0c7fd1',
+  'cros_components_revision': 'd04234ed17e4e8d2d95dc360b3bc3b0e6b0e3d6d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -824,7 +824,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '558518c5aafdd8733412f00a962739724caea90f',
+    '3349b81469925ee73aa3f70a67af7f485f9798c6',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -986,7 +986,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'lVVaxxGWEoKfi-XMLQyERTC8Smt04PrfwkbezmNSAVoC',
+          'version': 'iOMhhch3r8SFts_GRS6uFcUZVmbZ_r0A1NXDpyS1Z64C',
       },
     ],
     'condition': 'checkout_android',
@@ -1224,7 +1224,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '696d434318c314d5bbdbfd9fcd5102707bd48bba',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'c03a3a50f1ade6eee1ef4e77bfd355469a10b38f',
     'condition': 'checkout_src_internal',
   },
 
@@ -1689,7 +1689,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '6ba75cc5d93c39d4f86e2777709a460b30ce06fc',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '867067368dec61b75f63cf6798c25c49670d1e8c',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -2008,7 +2008,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': '_rX_N8Me5oLyS71fB-V0tKB3swszffgV4O24PY-ShfoC',
+        'version': 'TH-wuXVDHiIWULgejU4_yUGTfTbo1Fs2aL04wW-heI0C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -2041,7 +2041,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'XKzBi6GLYeg5A8F52tBTi7-E5TbR7p8NRud3PJxC24IC',
+        'version': 'umUfVfV7s1mHObWf589DE0XWF6Gou7tg7po7rp3lgRsC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4031,7 +4031,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        'db86abedd528e43509aea0f3aa9e7720a3a1ca91',
+        '37da2fd52608c0c45a507747657e6161829181e3',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index ed33cc0..5ae3ab1 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -2710,12 +2710,6 @@
              "UseAndroidStagingSmds",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Uses AuthSessionStorage signleton class instead of Profile-keyed
-// QuickUnlockStorage to store authenticated UserContext.
-BASE_FEATURE(kUseAuthSessionStorage,
-             "UseAuthSessionStorage",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // When enabled, the login shelf view is placed in its own widget instead of
 // sharing the shelf widget with other components.
 BASE_FEATURE(kUseLoginShelfWidget,
@@ -4333,10 +4327,6 @@
   return base::FeatureList::IsEnabled(kUseStorkSmdsServerAddress);
 }
 
-bool ShouldUseAuthSessionStorage() {
-  return base::FeatureList::IsEnabled(kUseAuthSessionStorage);
-}
-
 bool IsUserEducationEnabled() {
   return IsHoldingSpaceWallpaperNudgeEnabled() || IsWelcomeTourEnabled();
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 24c8246a..daf84f3 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -831,7 +831,6 @@
 BASE_DECLARE_FEATURE(kUpstreamTrustedReportsFirmware);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kUseAndroidStagingSmds);
-COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kUseAuthSessionStorage);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kUseLoginShelfWidget);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kUseMessagesStagingUrl);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -1269,7 +1268,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool ShouldOnlyShowNewShortcutApp();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool ShouldUseAndroidStagingSmds();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool ShouldUseStorkSmds();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool ShouldUseAuthSessionStorage();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool ShouldUseMappableSharedImage();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool isSearchCustomizableShortcutsInLauncherEnabled();
diff --git a/ash/webui/camera_app_ui/resources/js/barcode_chip.ts b/ash/webui/camera_app_ui/resources/js/barcode_chip.ts
index 6dc37a95..665cdac 100644
--- a/ash/webui/camera_app_ui/resources/js/barcode_chip.ts
+++ b/ash/webui/camera_app_ui/resources/js/barcode_chip.ts
@@ -5,6 +5,7 @@
 import {assert} from './assert.js';
 import * as dom from './dom.js';
 import {reportError} from './error.js';
+import {Flag} from './flag.js';
 import {I18nString} from './i18n_string.js';
 import {BarcodeContentType, sendBarcodeDetectedEvent} from './metrics.js';
 import * as loadTimeData from './models/load_time_data.js';
@@ -17,6 +18,15 @@
   ErrorType,
 } from './type.js';
 
+interface WifiConfig {
+  securityType: string;
+  ssid: string;
+  password: string;
+  hidden: boolean;
+}
+
+const QR_CODE_ESCAPE_CHARS = ['\\', ';', ',', ':'];
+
 // TODO(b/172879638): Tune the duration according to the final motion spec.
 const CHIP_DURATION = 8000;
 
@@ -83,6 +93,55 @@
 }
 
 /**
+ * Parses the given string `s`. If the string is a wifi connection request,
+ * return `WifiConfig` and if not, return null.
+ */
+function parseWifi(s: string): WifiConfig|null {
+  // Example string `WIFI:S:<SSID>;P:<PASSWORD>;T:<WPA|WEP|WPA2-EAP|nopass>;H;;`
+  const wifiConfig =
+      {securityType: 'nopass', ssid: '', password: '', hidden: false};
+  if (s.startsWith('WIFI:') && s.endsWith(';;')) {
+    s = s.substring(5, s.length - 1);
+    let i = 0;
+    let component = '';
+    while (i < s.length) {
+      // Unescape characters escaped with a backslash
+      if (s[i] === '\\' && i + 1 < s.length &&
+          QR_CODE_ESCAPE_CHARS.includes(s[i + 1])) {
+        component += s[i + 1];
+        i += 2;
+      } else if (s[i] === ';') {
+        const splitIdx = component.search(':');
+        if (splitIdx === -1) {
+          return null;
+        }
+        const key = component.substring(0, splitIdx);
+        const val = component.substring(splitIdx + 1);
+        if (key === 'T') {
+          wifiConfig.securityType = val;
+        } else if (key === 'S') {
+          wifiConfig.ssid = val;
+        } else if (key === 'P') {
+          wifiConfig.password = val;
+        } else if (key === 'H') {
+          wifiConfig.hidden = true;
+        }
+        component = '';
+        i += 1;
+      } else {
+        component += s[i];
+        i += 1;
+      }
+    }
+  }
+
+  if (wifiConfig.ssid === '') {
+    return null;
+  }
+  return wifiConfig;
+}
+
+/**
  * Creates the copy button.
  *
  * @param container The container for the button.
@@ -177,8 +236,17 @@
   }
 
   currentCode = code;
-
-  if (isSafeUrl(code)) {
+  const wifiConfig = parseWifi(code);
+  if (loadTimeData.getChromeFlag(Flag.AUTO_QR) && wifiConfig !== null) {
+    sendBarcodeDetectedEvent(
+        {contentType: BarcodeContentType.WIFI}, wifiConfig.securityType);
+    if (!['WPA', 'WEP', 'WPA2-EAP', 'nopass'].includes(
+            wifiConfig.securityType)) {
+      // For unsupported security types, we show a raw string.
+      showText(code);
+    }
+    // TODO(dorahkim): If securityType is supported, showWifi().
+  } else if (isSafeUrl(code)) {
     sendBarcodeDetectedEvent({contentType: BarcodeContentType.URL});
     showUrl(code);
   } else {
diff --git a/ash/webui/camera_app_ui/resources/js/metrics.ts b/ash/webui/camera_app_ui/resources/js/metrics.ts
index a13c637c..b1a5f24d 100644
--- a/ash/webui/camera_app_ui/resources/js/metrics.ts
+++ b/ash/webui/camera_app_ui/resources/js/metrics.ts
@@ -509,6 +509,7 @@
 export enum BarcodeContentType {
   TEXT = 'text',
   URL = 'url',
+  WIFI = 'wifi',
 }
 
 interface BarcodeDetectedEventParam {
@@ -519,12 +520,17 @@
  * Sends the barcode detected event.
  */
 export function sendBarcodeDetectedEvent(
-    {contentType}: BarcodeDetectedEventParam): void {
-  sendEvent({
-    eventCategory: 'barcode',
-    eventAction: 'detect',
-    eventLabel: contentType,
-  });
+    {contentType}: BarcodeDetectedEventParam,
+    wifiSecurityType: string = ''): void {
+  sendEvent(
+      {
+        eventCategory: 'barcode',
+        eventAction: 'detect',
+        eventLabel: contentType,
+      },
+      new Map([
+        [GaMetricDimension.WIFI_SECURITY_TYPE, wifiSecurityType],
+      ]));
 }
 
 /**
diff --git a/ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.ts b/ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.ts
index fd5407e..864bad7a 100644
--- a/ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.ts
+++ b/ash/webui/camera_app_ui/resources/js/untrusted_ga_helper.ts
@@ -89,6 +89,7 @@
   IS_TEST_IMAGE = 38,
   DEVICE_PIXEL_RATIO = 39,
   CAMERA_MODULE_ID = 40,
+  WIFI_SECURITY_TYPE = 41,
 }
 
 export enum Ga4MetricDimension {
@@ -138,6 +139,7 @@
   TALL_ORIENTATION = 'tall_orientation',
   TIME_LAPSE_SPEED = 'time_lapse_speed',
   TIMER = 'timer',
+  WIFI_SECURITY_TYPE = 'wifi_security_type',
 }
 
 export type Ga4EventParams =
diff --git a/build/config/siso/cros.star b/build/config/siso/cros.star
index a5faff5..b33a094 100644
--- a/build/config/siso/cros.star
+++ b/build/config/siso/cros.star
@@ -77,6 +77,7 @@
             step_config["rules"].extend([
                 {
                     "name": "clang-cros/cxx",
+                    "action": "(.*_)?cxx",
                     "command_prefix": "../../build/cros_cache/chrome-sdk/",
                     "remote": True,
                     "handler": "cros_compiler",
@@ -85,6 +86,7 @@
                 },
                 {
                     "name": "clang-cros/cc",
+                    "action": "(.*_)?cc",
                     "command_prefix": "../../build/cros_cache/chrome-sdk/",
                     "remote": True,
                     "handler": "cros_compiler",
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java
index 74212a4..dee09f6 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessoryIntegrationTest.java
@@ -34,6 +34,7 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisableIf;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper;
@@ -188,6 +189,7 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.RECOVER_FROM_NEVER_SAVE_ANDROID)
+    @DisabledTest(message = "https://crbug.com/1503085")
     public void testEnablesUndenylistingToggle() throws TimeoutException, InterruptedException {
         preparePasswordBridge();
         String url = mTestServer.getURL("/chrome/test/data/password/password_form.html");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappPermissionManager.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappPermissionManager.java
index 7c89b8c..e290c19 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappPermissionManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappPermissionManager.java
@@ -129,19 +129,6 @@
         String appName = getAppNameForPackage(packageName);
         if (appName == null) return;
 
-        if (type == ContentSettingsType.GEOLOCATION) {
-            boolean enabled = settingValue == ContentSettingValues.ALLOW;
-            @ContentSettingValues
-            Integer lastPermissionSetting = mStore.getPermission(type, origin);
-            Boolean lastPermissionBoolean;
-            if (lastPermissionSetting == null) {
-                lastPermissionBoolean = null;
-            } else {
-                lastPermissionBoolean = lastPermissionSetting == ContentSettingValues.ALLOW;
-            }
-            mUmaRecorder.recordLocationPermissionChanged(lastPermissionBoolean, enabled);
-        }
-
         // It's important that we set the state before we destroy the notification channel. If we
         // did it the other way around there'd be a small moment in time where the website's
         // notification permission could flicker from SET -> UNSET -> SET. This way we transition
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/LocationPermissionUpdater.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/LocationPermissionUpdater.java
index 0207a1d..9ba676ab 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/LocationPermissionUpdater.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/LocationPermissionUpdater.java
@@ -82,7 +82,6 @@
             @ContentSettingValues int settingValue) {
         boolean enabled = settingValue == ContentSettingValues.ALLOW;
         mPermissionManager.updatePermission(origin, app.getPackageName(), TYPE, settingValue);
-        mUmaRecorder.recordLocationPermissionRequestResult(enabled);
         Log.d(TAG, "Updating origin location permissions to: %b", enabled);
 
         InstalledWebappBridge.runPermissionCallback(callback, settingValue);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index f5235b2..1aa3d61 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -126,34 +126,6 @@
     @VisibleForTesting
     static final String ON_DETACHED_REQUEST_COMPLETED = "onDetachedRequestCompleted";
 
-    // For SpeculationStatusOnStart status.
-    // TODO(crbug.com/1384816): remove if applicable.
-    @VisibleForTesting
-    private static final int SPECULATION_STATUS_ON_START_ALLOWED = 0;
-
-    // What kind of speculation was started, counted in addition to
-    // SPECULATION_STATUS_ALLOWED.
-    private static final int SPECULATION_STATUS_ON_START_PRERENDER = 2;
-    private static final int SPECULATION_STATUS_ON_START_BACKGROUND_TAB = 3;
-    // The following describe reasons why a speculation was not allowed, and are
-    // counted instead of SPECULATION_STATUS_ALLOWED.
-    private static final int SPECULATION_STATUS_ON_START_NOT_ALLOWED_DEVICE_CLASS = 5;
-    private static final int SPECULATION_STATUS_ON_START_NOT_ALLOWED_BLOCK_3RD_PARTY_COOKIES = 6;
-    private static final int SPECULATION_STATUS_ON_START_NOT_ALLOWED_NETWORK_PREDICTION_DISABLED =
-            7;
-    // Obsolete due to no longer running the experiment
-    // "PredictivePrefetchingAllowedOnAllConnectionTypes".
-    // private static final int SPECULATION_STATUS_ON_START_NOT_ALLOWED_NETWORK_METERED = 9;
-
-    // Obsolete due to expired histogram.
-    //private static final int SPECULATION_STATUS_ON_START_MAX = 10;
-
-    // For CustomTabs.SpeculationStatusOnSwap, see tools/metrics/enums.xml. Append only.
-    // Obsolete due to expired histogram.
-    // private static final int SPECULATION_STATUS_ON_SWAP_BACKGROUND_TAB_TAKEN = 0;
-    // private static final int SPECULATION_STATUS_ON_SWAP_BACKGROUND_TAB_NOT_MATCHED = 1;
-    // private static final int SPECULATION_STATUS_ON_SWAP_MAX = 4;
-
     // Constants for sending connection characteristics.
     public static final String DATA_REDUCTION_ENABLED = "dataReductionEnabled";
 
@@ -1708,24 +1680,18 @@
         }
     }
 
-    @VisibleForTesting
-    int maySpeculateWithResult(CustomTabsSessionToken session) {
+    boolean maySpeculate(CustomTabsSessionToken session) {
         if (!DeviceClassManager.enablePrerendering()) {
-            return SPECULATION_STATUS_ON_START_NOT_ALLOWED_DEVICE_CLASS;
+            return false;
         }
         if (UserPrefs.get(Profile.getLastUsedRegularProfile()).getInteger(COOKIE_CONTROLS_MODE)
                 == CookieControlsMode.BLOCK_THIRD_PARTY) {
-            return SPECULATION_STATUS_ON_START_NOT_ALLOWED_BLOCK_3RD_PARTY_COOKIES;
+            return false;
         }
         if (PreloadPagesSettingsBridge.getState() == PreloadPagesState.NO_PRELOADING) {
-            return SPECULATION_STATUS_ON_START_NOT_ALLOWED_NETWORK_PREDICTION_DISABLED;
+            return false;
         }
-        return SPECULATION_STATUS_ON_START_ALLOWED;
-    }
-
-    boolean maySpeculate(CustomTabsSessionToken session) {
-        int speculationResult = maySpeculateWithResult(session);
-        return speculationResult == SPECULATION_STATUS_ON_START_ALLOWED;
+        return true;
     }
 
     /** Cancels the speculation for a given session, or any session if null. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncher.java
index d7fbe441..cea894e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncher.java
@@ -23,14 +23,14 @@
  */
 public class PasswordCheckupLauncher {
     @CalledByNative
-    private static void launchCheckupInAccountWithWindowAndroid(
+    private static void launchCheckupOnlineWithWindowAndroid(
             String checkupUrl, WindowAndroid windowAndroid) {
         if (windowAndroid.getContext().get() == null) return; // Window not available yet/anymore.
-        launchCheckupInAccountWithActivity(checkupUrl, windowAndroid.getActivity().get());
+        launchCheckupOnlineWithActivity(checkupUrl, windowAndroid.getActivity().get());
     }
 
     @CalledByNative
-    private static void launchLocalCheckup(
+    private static void launchCheckupOnDevice(
             WindowAndroid windowAndroid, @PasswordCheckReferrer int passwordCheckReferrer) {
         if (windowAndroid.getContext().get() == null) return; // Window not available yet/anymore.
 
@@ -46,7 +46,7 @@
     }
 
     @CalledByNative
-    private static void launchCheckupInAccountWithActivity(String checkupUrl, Activity activity) {
+    private static void launchCheckupOnlineWithActivity(String checkupUrl, Activity activity) {
         if (tryLaunchingNativePasswordCheckup(activity)) return;
         Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(checkupUrl));
         intent.setPackage(activity.getPackageName());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/OWNERS
index 613f447..8c01572 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/OWNERS
@@ -1,3 +1 @@
 file://chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS
-per-file *CustomTabActivityIncognitoTest*=roagarwal@chromium.org
-per-file *CustomTabActivityIncognitoMetricTest*=roagarwal@chromium.org
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 717e55f8..3393bc4 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
@@ -14,7 +14,9 @@
 import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
 import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
 import static androidx.test.espresso.matcher.ViewMatchers.hasSibling;
+import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withChild;
 import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
@@ -35,7 +37,11 @@
 import static org.chromium.components.content_settings.PrefNames.DESKTOP_SITE_DISPLAY_SETTING_ENABLED;
 import static org.chromium.components.content_settings.PrefNames.DESKTOP_SITE_PERIPHERAL_SETTING_ENABLED;
 import static org.chromium.components.content_settings.PrefNames.DESKTOP_SITE_WINDOW_SETTING_ENABLED;
+import static org.chromium.ui.test.util.ViewUtils.VIEW_GONE;
+import static org.chromium.ui.test.util.ViewUtils.VIEW_INVISIBLE;
+import static org.chromium.ui.test.util.ViewUtils.VIEW_NULL;
 import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
+import static org.chromium.ui.test.util.ViewUtils.waitForViewCheckingState;
 
 import android.content.Context;
 import android.content.Intent;
@@ -47,6 +53,7 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceScreen;
 import androidx.test.core.app.ApplicationProvider;
+import androidx.test.espresso.ViewInteraction;
 import androidx.test.espresso.matcher.ViewMatchers;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
@@ -103,6 +110,7 @@
 import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
 import org.chromium.components.browser_ui.settings.ExpandablePreferenceGroup;
 import org.chromium.components.browser_ui.settings.SettingsLauncher;
+import org.chromium.components.browser_ui.site_settings.ContentSettingException;
 import org.chromium.components.browser_ui.site_settings.ContentSettingsResources;
 import org.chromium.components.browser_ui.site_settings.FPSCookieSettings;
 import org.chromium.components.browser_ui.site_settings.FourStateCookieSettingsPreference;
@@ -114,6 +122,7 @@
 import org.chromium.components.browser_ui.site_settings.SiteSettings;
 import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory;
 import org.chromium.components.browser_ui.site_settings.SiteSettingsFeatureList;
+import org.chromium.components.browser_ui.site_settings.StorageAccessSubpageSettings;
 import org.chromium.components.browser_ui.site_settings.TriStateCookieSettingsPreference;
 import org.chromium.components.browser_ui.site_settings.TriStateSiteSettingsPreference;
 import org.chromium.components.browser_ui.site_settings.Website;
@@ -349,12 +358,44 @@
                     WebsitePreferenceBridge.setContentSettingCustomScope(
                             getBrowserContextHandle(),
                             ContentSettingsType.STORAGE_ACCESS,
+                            "primary.com",
+                            "secondary3.com",
+                            ContentSettingValues.ALLOW);
+                    WebsitePreferenceBridge.setContentSettingCustomScope(
+                            getBrowserContextHandle(),
+                            ContentSettingsType.STORAGE_ACCESS,
                             "primary2.com",
                             "secondary2.com",
                             ContentSettingValues.ALLOW);
                 });
     }
 
+    private Website getStorageAccessSite() {
+        WebsiteAddress permissionOrigin = WebsiteAddress.create("primary.com");
+        WebsiteAddress permissionEmbedder = WebsiteAddress.create("*");
+        Website site = new Website(permissionOrigin, permissionEmbedder);
+        site.addEmbeddedPermission(
+                new ContentSettingException(
+                        ContentSettingsType.STORAGE_ACCESS,
+                        "primary.com",
+                        "secondary1.com",
+                        ContentSettingValues.ALLOW,
+                        "preference",
+                        30,
+                        false));
+        site.addEmbeddedPermission(
+                new ContentSettingException(
+                        ContentSettingsType.STORAGE_ACCESS,
+                        "primary.com",
+                        "secondary3.com",
+                        ContentSettingValues.ALLOW,
+                        "preference",
+                        30,
+                        false));
+
+        return site;
+    }
+
     /** Sets Allow Location Enabled to be true and make sure it is set correctly. */
     @Test
     @SmallTest
@@ -1523,33 +1564,106 @@
     @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testExpectedExceptionsStorageAccess() {
         createStorageAccessExceptions();
-        SiteSettingsTestUtils.startSiteSettingsCategory(SiteSettingsCategory.Type.STORAGE_ACCESS);
+        final SettingsActivity settingsActivity =
+                SiteSettingsTestUtils.startSiteSettingsCategory(
+                        SiteSettingsCategory.Type.STORAGE_ACCESS);
 
         onView(withText("primary.com")).check(matches(isDisplayed()));
-        onView(withText("Embedded on secondary.com")).check(matches(isDisplayed()));
+        onView(withText("2 sites")).check(matches(isDisplayed()));
         onView(withText("primary2.com")).check(matches(isDisplayed()));
-        onView(withText("Embedded on secondary2.com")).check(matches(isDisplayed()));
+        onView(withText("1 site")).check(matches(isDisplayed()));
 
-        onView(withText("primary.com")).perform(click());
-        onView(withText("Block")).perform(click());
+        SingleCategorySettings websitePreferences =
+                (SingleCategorySettings) settingsActivity.getMainFragment();
+        ExpandablePreferenceGroup managedGroup =
+                (ExpandablePreferenceGroup)
+                        websitePreferences.findPreference(SingleCategorySettings.ALLOWED_GROUP);
+
+        ChromeImageViewPreference websitePreference =
+                (ChromeImageViewPreference) managedGroup.getPreference(0);
+
+        Mockito.clearInvocations(mSettingsLauncher);
+        websitePreferences.setSettingsLauncher(mSettingsLauncher);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    // Click on the chevron button of the first permission group.
+                    websitePreference.getButton().performClick();
+                });
+
+        Mockito.verify(mSettingsLauncher)
+                .launchSettingsActivity(
+                        eq(websitePreferences.getContext()),
+                        eq(StorageAccessSubpageSettings.class),
+                        Mockito.any(Bundle.class));
+
+        // TODO(http://b/307249155): Test deletion for a group of storage access.
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Preferences"})
+    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
+    public void testStorageAccessSubpage() {
+        createStorageAccessExceptions();
+        final SettingsActivity settingsActivity =
+                SiteSettingsTestUtils.startStorageAccessSettingsActivity(getStorageAccessSite());
+
+        onViewWaiting(withText("secondary1.com")).check(matches(isDisplayed()));
+        onViewWaiting(withText("secondary3.com")).check(matches(isDisplayed()));
+
+        StorageAccessSubpageSettings embeddedWebsitePreferences =
+                (StorageAccessSubpageSettings) settingsActivity.getMainFragment();
+
+        // Reset first permission.
+        getImageViewWidget("secondary1.com").check(matches(isDisplayed())).perform(click());
 
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     assertEquals(
-                            ContentSettingValues.BLOCK,
+                            ContentSettingValues.ASK,
                             WebsitePreferenceBridge.getContentSetting(
                                     getBrowserContextHandle(),
                                     ContentSettingsType.STORAGE_ACCESS,
                                     new GURL("https://primary.com"),
-                                    new GURL("https://secondary.com")));
+                                    new GURL("https://secondary1.com")));
                     assertEquals(
                             ContentSettingValues.ALLOW,
                             WebsitePreferenceBridge.getContentSetting(
                                     getBrowserContextHandle(),
                                     ContentSettingsType.STORAGE_ACCESS,
-                                    new GURL("https://primary2.com"),
-                                    new GURL("https://secondary2.com")));
+                                    new GURL("https://primary.com"),
+                                    new GURL("https://secondary3.com")));
                 });
+
+        waitForViewCheckingState(
+                withText("secondary1.com"), VIEW_INVISIBLE | VIEW_NULL | VIEW_GONE);
+        onView(withText("secondary1.com")).check(doesNotExist());
+        onView(withText("secondary3.com")).check(matches(isDisplayed()));
+
+        // Reset second permission.
+        getImageViewWidget("secondary3.com").check(matches(isDisplayed())).perform(click());
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    assertEquals(
+                            ContentSettingValues.ASK,
+                            WebsitePreferenceBridge.getContentSetting(
+                                    getBrowserContextHandle(),
+                                    ContentSettingsType.STORAGE_ACCESS,
+                                    new GURL("https://primary.com"),
+                                    new GURL("https://secondary3.com")));
+                });
+
+        // Check that, because there aren't any permissions to show, the activity is closed.
+        Assert.assertTrue(settingsActivity.isFinishing());
+    }
+
+    private ViewInteraction getImageViewWidget(String preferenceTitle) {
+        return onView(
+                allOf(
+                        withId(R.id.image_view_widget),
+                        isDescendantOfA(withChild(withChild(withText(preferenceTitle))))));
     }
 
     @Test
@@ -2682,20 +2796,46 @@
         settingsActivity.finish();
     }
 
-    private void renderCategoryPage(@SiteSettingsCategory.Type int category, String name)
+    private void renderSettingsPage(SettingsActivity settingsActivity, String name)
             throws IOException {
-        createCookieExceptions();
-        var settingsActivity = SiteSettingsTestUtils.startSiteSettingsCategory(category);
         View view = settingsActivity.findViewById(android.R.id.content).getRootView();
         ChromeRenderTestRule.sanitize(view);
         mRenderTestRule.render(view, name);
         settingsActivity.finish();
     }
 
+    private void renderCategoryPage(@SiteSettingsCategory.Type int category, String name)
+            throws IOException {
+        var settingsActivity = SiteSettingsTestUtils.startSiteSettingsCategory(category);
+        renderSettingsPage(settingsActivity, name);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"RenderTest"})
+    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
+    public void testRenderStorageAccessPage() throws Exception {
+        createStorageAccessExceptions();
+        renderCategoryPage(
+                SiteSettingsCategory.Type.STORAGE_ACCESS, "site_settings_storage_access_page");
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"RenderTest"})
+    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
+    public void testRenderStorageAccessSubpage() throws Exception {
+        createStorageAccessExceptions();
+        final SettingsActivity settingsActivity =
+                SiteSettingsTestUtils.startStorageAccessSettingsActivity(getStorageAccessSite());
+        renderSettingsPage(settingsActivity, "site_settings_storage_access_subpage");
+    }
+
     @Test
     @SmallTest
     @Feature({"RenderTest"})
     public void testRenderSiteDataPage() throws Exception {
+        createCookieExceptions();
         renderCategoryPage(SiteSettingsCategory.Type.SITE_DATA, "site_settings_site_data_page");
     }
 
@@ -2704,6 +2844,7 @@
     @Feature({"RenderTest"})
     @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_FPS_UI)
     public void testRenderThirdPartyCookiesPage() throws Exception {
+        createCookieExceptions();
         renderCategoryPage(
                 SiteSettingsCategory.Type.THIRD_PARTY_COOKIES,
                 "site_settings_third_party_cookies_page");
@@ -2714,6 +2855,7 @@
     @Feature({"RenderTest"})
     @EnableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_FPS_UI})
     public void testRenderThirdPartyCookiesPageWithFPS() throws Exception {
+        createCookieExceptions();
         renderCategoryPage(
                 SiteSettingsCategory.Type.THIRD_PARTY_COOKIES,
                 "site_settings_third_party_cookies_page_fps");
@@ -2724,6 +2866,7 @@
     @Feature({"RenderTest"})
     @DisableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_FPS_UI})
     public void testRenderCookiesPageThirdPartyCookiesPageWithoutFPS() throws Exception {
+        createCookieExceptions();
         renderCategoryPage(
                 SiteSettingsCategory.Type.THIRD_PARTY_COOKIES,
                 "site_settings_third_party_cookies_page_without_fps");
@@ -2734,6 +2877,7 @@
     @Feature({"RenderTest"})
     @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_FPS_UI)
     public void testRenderCookiesPageWithFPS() throws Exception {
+        createCookieExceptions();
         renderCategoryPage(SiteSettingsCategory.Type.COOKIES, "site_settings_cookies_page_fps");
     }
 
@@ -2741,6 +2885,7 @@
     @SmallTest
     @Feature({"RenderTest"})
     public void testRenderLocationPage() throws Exception {
+        createCookieExceptions();
         renderCategoryPage(
                 SiteSettingsCategory.Type.DEVICE_LOCATION, "site_settings_location_page");
     }
@@ -2749,6 +2894,7 @@
     @SmallTest
     @Feature({"RenderTest"})
     public void testRenderProtectedMediaPage() throws Exception {
+        createCookieExceptions();
         renderCategoryPage(
                 SiteSettingsCategory.Type.PROTECTED_MEDIA, "site_settings_protected_media_page");
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTestUtils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTestUtils.java
index 76d8548..87e10ea 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTestUtils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTestUtils.java
@@ -24,6 +24,7 @@
 import org.chromium.components.browser_ui.site_settings.SingleWebsiteSettings;
 import org.chromium.components.browser_ui.site_settings.SiteSettings;
 import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory;
+import org.chromium.components.browser_ui.site_settings.StorageAccessSubpageSettings;
 import org.chromium.components.browser_ui.site_settings.Website;
 import org.chromium.components.browser_ui.site_settings.WebsiteGroup;
 import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
@@ -73,6 +74,20 @@
                 InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
     }
 
+    public static SettingsActivity startStorageAccessSettingsActivity(Website site) {
+        Bundle fragmentArgs = new Bundle();
+        fragmentArgs.putSerializable(StorageAccessSubpageSettings.EXTRA_STORAGE_ACCESS_STATE, site);
+        fragmentArgs.putBoolean(StorageAccessSubpageSettings.EXTRA_ALLOWED, true);
+
+        SettingsLauncher settingsLauncher = new SettingsLauncherImpl();
+        Context context = ApplicationProvider.getApplicationContext();
+        Intent intent =
+                settingsLauncher.createSettingsActivityIntent(
+                        context, StorageAccessSubpageSettings.class.getName(), fragmentArgs);
+        return (SettingsActivity)
+                InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
+    }
+
     public static SettingsActivity startSingleWebsitePreferences(Website site) {
         Bundle fragmentArgs = new Bundle();
         fragmentArgs.putSerializable(SingleWebsiteSettings.EXTRA_SITE, site);
diff --git a/chrome/android/profiles/arm.newest.txt b/chrome/android/profiles/arm.newest.txt
index 0cde0d8..3be149f2 100644
--- a/chrome/android/profiles/arm.newest.txt
+++ b/chrome/android/profiles/arm.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-121.0.6126.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-arm-121.0.6128.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 4f179e2..680e2c9 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -15466,22 +15466,6 @@
       }
     </message>
 
-    <!-- User happiness tracking survey UI -->
-    <if expr="not is_android">
-      <if expr="use_titlecase">
-        <message name="IDS_HATS_BUBBLE_OK_LABEL" translateable="false" desc="Button label on Happiness Tracking Survey's invitation banner, clicking the button gives the user's consent on taking the survey.">
-          Take Survey
-        </message>
-      </if>
-      <if expr="not use_titlecase">
-        <message name="IDS_HATS_BUBBLE_OK_LABEL" translateable="false" desc="Button label on Happiness Tracking Survey's invitation banner, clicking the button gives the user's consent on taking the survey.">
-          Take survey
-        </message>
-      </if>
-      <message name="IDS_HATS_BUBBLE_TEXT" translateable="false" desc="The text in Happiness Tracking Survey's invitation banner, it explains why users should take our survey.">
-        Your feedback is important to us.
-      </message>
-    </if>
     <message name="IDS_NOTIFICATION_DEFAULT_HELPFUL_BUTTON_TEXT" desc="Text body for the helpful button in notifications sent from notification scheduler.">
       Helpful
     </message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 0205844..739d9738 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -8267,6 +8267,12 @@
      FEATURE_VALUE_TYPE(features::kElasticOverscroll)},
 #endif
 
+#if !BUILDFLAG(IS_ANDROID)
+    {"element-capture", flag_descriptions::kElementCaptureName,
+     flag_descriptions::kElementCaptureDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(blink::features::kElementCapture)},
+#endif
+
     {"device-posture", flag_descriptions::kDevicePostureName,
      flag_descriptions::kDevicePostureDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kDevicePosture)},
diff --git a/chrome/browser/android/browserservices/metrics/java/src/org/chromium/chrome/browser/browserservices/metrics/TrustedWebActivityUmaRecorder.java b/chrome/browser/android/browserservices/metrics/java/src/org/chromium/chrome/browser/browserservices/metrics/TrustedWebActivityUmaRecorder.java
index 882043d..a2254d211 100644
--- a/chrome/browser/android/browserservices/metrics/java/src/org/chromium/chrome/browser/browserservices/metrics/TrustedWebActivityUmaRecorder.java
+++ b/chrome/browser/android/browserservices/metrics/java/src/org/chromium/chrome/browser/browserservices/metrics/TrustedWebActivityUmaRecorder.java
@@ -205,32 +205,6 @@
         }
     }
 
-    public void recordLocationPermissionChanged(Boolean last, boolean enabled) {
-        @Nullable
-        @PermissionChanged
-        Integer change = null;
-        if (last == null) {
-            if (enabled) {
-                change = PermissionChanged.NULL_TO_TRUE;
-            } else {
-                change = PermissionChanged.NULL_TO_FALSE;
-            }
-        } else {
-            if (last && !enabled) change = PermissionChanged.TRUE_TO_FALSE;
-            if (!last && enabled) change = PermissionChanged.FALSE_TO_TRUE;
-        }
-        if (change != null) {
-            RecordHistogram.recordEnumeratedHistogram(
-                    "TrustedWebActivity.LocationPermissionChanged", change,
-                    PermissionChanged.NUM_ENTRIES);
-        }
-    }
-
-    public void recordLocationPermissionRequestResult(boolean enabled) {
-        RecordHistogram.recordBooleanHistogram(
-                "TrustedWebActivity.LocationPermissionRequestIsGranted", enabled);
-    }
-
     public void recordExtraCommandSuccess(String command, boolean success) {
         RecordHistogram.recordBooleanHistogram(
                 "TrustedWebActivity.ExtraCommandSuccess." + command, success);
diff --git a/chrome/browser/app_controller_mac.mm b/chrome/browser/app_controller_mac.mm
index 56477e06..01db869 100644
--- a/chrome/browser/app_controller_mac.mm
+++ b/chrome/browser/app_controller_mac.mm
@@ -846,6 +846,10 @@
 
   _isShuttingDown = true;
 
+  // `_historyMenuBridge` has a dependency on `_lastProfile`, so that’s why it’s
+  // deleted first.
+  _historyMenuBridge.reset();
+
   // It's safe to delete |_lastProfile| now.
   [self setLastProfile:nullptr];
 
diff --git a/chrome/browser/ash/crosapi/in_session_auth_ash.cc b/chrome/browser/ash/crosapi/in_session_auth_ash.cc
index ed98f9d..9e34bd80 100644
--- a/chrome/browser/ash/crosapi/in_session_auth_ash.cc
+++ b/chrome/browser/ash/crosapi/in_session_auth_ash.cc
@@ -6,12 +6,9 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/in_session_auth_dialog_controller.h"
-#include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
-#include "base/json/values_util.h"
-#include "base/notreached.h"
 #include "chrome/browser/ash/login/quick_unlock/auth_token.h"
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h"
@@ -53,31 +50,13 @@
                                   const std::string& token,
                                   CheckTokenCallback callback) {
   bool token_valid;
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    token_valid = ash::AuthSessionStorage::Get()->IsValid(token);
-  } else {
-    auto account_id =
-        ash::Shell::Get()->session_controller()->GetActiveAccountId();
-
-    ash::quick_unlock::QuickUnlockStorage* quick_unlock_storage =
-        ash::quick_unlock::QuickUnlockFactory::GetForAccountId(account_id);
-    const ash::quick_unlock::AuthToken* auth_token =
-        quick_unlock_storage->GetAuthToken();
-    token_valid =
-        auth_token != nullptr && auth_token->GetAge().has_value() &&
-        token == auth_token->Identifier() &&
-        auth_token->GetAge() <= ash::quick_unlock::AuthToken::kTokenExpiration;
-  }
+  token_valid = ash::AuthSessionStorage::Get()->IsValid(token);
 
   std::move(callback).Run(token_valid);
 }
 
 void InSessionAuthAsh::InvalidateToken(const std::string& token) {
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    ash::AuthSessionStorage::Get()->Invalidate(token, base::DoNothing());
-  } else {
-    NOTIMPLEMENTED();
-  }
+  ash::AuthSessionStorage::Get()->Invalidate(token, base::DoNothing());
 }
 
 void InSessionAuthAsh::OnAuthComplete(RequestTokenCallback callback,
diff --git a/chrome/browser/ash/login/quick_unlock/pin_backend.cc b/chrome/browser/ash/login/quick_unlock/pin_backend.cc
index 62f6dac..b3ab72a4 100644
--- a/chrome/browser/ash/login/quick_unlock/pin_backend.cc
+++ b/chrome/browser/ash/login/quick_unlock/pin_backend.cc
@@ -206,28 +206,14 @@
   DCHECK(storage);
 
   if (cryptohome_backend_) {
-    std::unique_ptr<UserContext> user_context;
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      if (!ash::AuthSessionStorage::Get()->IsValid(token)) {
-        PostResponse(std::move(did_set), false);
-        return;
-      }
-      ash::AuthSessionStorage::Get()->BorrowAsync(
-          FROM_HERE, token,
-          base::BindOnce(&PinBackend::SetWithContext, base::Unretained(this),
-                         account_id, token, pin, std::move(did_set)));
+    if (!ash::AuthSessionStorage::Get()->IsValid(token)) {
+      PostResponse(std::move(did_set), false);
       return;
-    } else {
-      // If `user_context` is null, then the token timed out.
-      const UserContext* maybe_context = storage->GetUserContext(token);
-      if (!maybe_context) {
-        PostResponse(std::move(did_set), false);
-        return;
-      }
-      user_context = std::make_unique<UserContext>(*maybe_context);
-      SetWithContext(account_id, token, pin, std::move(did_set),
-                     std::make_unique<UserContext>(*maybe_context));
     }
+    ash::AuthSessionStorage::Get()->BorrowAsync(
+        FROM_HERE, token,
+        base::BindOnce(&PinBackend::SetWithContext, base::Unretained(this),
+                       account_id, token, pin, std::move(did_set)));
   } else {
     storage->pin_storage_prefs()->SetPin(pin);
     storage->MarkStrongAuth();
@@ -319,27 +305,14 @@
   UpdatePinAutosubmitOnRemove(account_id);
 
   if (cryptohome_backend_) {
-    std::unique_ptr<UserContext> user_context;
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      if (!ash::AuthSessionStorage::Get()->IsValid(token)) {
-        PostResponse(std::move(did_remove), false);
-        return;
-      }
-      ash::AuthSessionStorage::Get()->BorrowAsync(
-          FROM_HERE, token,
-          base::BindOnce(&PinBackend::RemoveWithContext, base::Unretained(this),
-                         account_id, token, std::move(did_remove)));
+    if (!ash::AuthSessionStorage::Get()->IsValid(token)) {
+      PostResponse(std::move(did_remove), false);
       return;
-    } else {
-      // If `user_context` is null, then the token timed out.
-      const UserContext* maybe_context = storage->GetUserContext(token);
-      if (!maybe_context) {
-        PostResponse(std::move(did_remove), false);
-        return;
-      }
-      RemoveWithContext(account_id, token, std::move(did_remove),
-                        std::make_unique<UserContext>(*maybe_context));
     }
+    ash::AuthSessionStorage::Get()->BorrowAsync(
+        FROM_HERE, token,
+        base::BindOnce(&PinBackend::RemoveWithContext, base::Unretained(this),
+                       account_id, token, std::move(did_remove)));
   } else {
     const bool had_pin = storage->pin_storage_prefs()->IsPinSet();
     storage->pin_storage_prefs()->RemovePin();
@@ -622,14 +595,7 @@
                                  BoolCallback callback,
                                  std::unique_ptr<UserContext> user_context,
                                  absl::optional<AuthenticationError> error) {
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    ash::AuthSessionStorage::Get()->Return(auth_token, std::move(user_context));
-  } else {
-    QuickUnlockStorage* storage = GetPrefsBackend(user_context->GetAccountId());
-    if (storage) {
-      storage->ReplaceUserContext(auth_token, std::move(user_context));
-    }
-  }
+  ash::AuthSessionStorage::Get()->Return(auth_token, std::move(user_context));
   std::move(callback).Run(!error.has_value());
 }
 
diff --git a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.cc b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.cc
index 321ca95d..a4edc375 100644
--- a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.cc
+++ b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.cc
@@ -73,13 +73,6 @@
          pin_storage_prefs()->TryAuthenticatePin(key, purpose);
 }
 
-std::string QuickUnlockStorage::CreateAuthToken(
-    const UserContext& user_context) {
-  auth_token_ = std::make_unique<AuthToken>(user_context);
-  DCHECK(auth_token_->Identifier().has_value());
-  return *auth_token_->Identifier();
-}
-
 AuthToken* QuickUnlockStorage::GetAuthToken() {
   if (!auth_token_ || !auth_token_->Identifier().has_value())
     return nullptr;
diff --git a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h
index 3f9c3ac..9f3537b 100644
--- a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h
+++ b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h
@@ -62,14 +62,7 @@
   // attempt. This always returns false if HasStrongAuth returns false.
   bool TryAuthenticatePin(const Key& key, Purpose purpose);
 
-  // Creates a new authentication token to be used by the quickSettingsPrivate
-  // API for authenticating requests. Resets the expiration timer and
-  // invalidates any previously issued tokens.
-  std::string CreateAuthToken(const UserContext& user_context);
-
-  // Returns true if the current authentication token has expired.
-  bool GetAuthTokenExpired();
-
+  // TODO(b/271249180): cleanup remaining AuthToken refs:
   // Returns the auth token if it is valid or nullptr if it is expired or has
   // not been created. May return nullptr.
   AuthToken* GetAuthToken();
@@ -101,6 +94,7 @@
 
   const raw_ptr<Profile, ExperimentalAsh> profile_;
   base::Time last_strong_auth_;
+  // TODO(b/271249180): cleanup remaining AuthToken refs:
   std::unique_ptr<AuthToken> auth_token_;
   raw_ptr<base::Clock, ExperimentalAsh> clock_;
   std::unique_ptr<FingerprintStorage> fingerprint_storage_;
diff --git a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage_unittest.cc b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage_unittest.cc
index 1854fe8..b3227f3 100644
--- a/chrome/browser/ash/login/quick_unlock/quick_unlock_storage_unittest.cc
+++ b/chrome/browser/ash/login/quick_unlock/quick_unlock_storage_unittest.cc
@@ -173,20 +173,5 @@
   EXPECT_TRUE(quick_unlock_storage->HasStrongAuth());
 }
 
-TEST_F(QuickUnlockStorageUnitTest, AuthToken) {
-  QuickUnlockStorage* quick_unlock_storage =
-      QuickUnlockFactory::GetForProfile(profile_.get());
-  EXPECT_FALSE(quick_unlock_storage->GetAuthToken());
-
-  UserContext context;
-  std::string auth_token = quick_unlock_storage->CreateAuthToken(context);
-  EXPECT_NE(std::string(), auth_token);
-  EXPECT_TRUE(quick_unlock_storage->GetAuthToken());
-  EXPECT_EQ(auth_token, quick_unlock_storage->GetAuthToken()->Identifier());
-
-  ExpireAuthToken();
-  EXPECT_FALSE(quick_unlock_storage->GetAuthToken());
-}
-
 }  // namespace quick_unlock
 }  // namespace ash
diff --git a/chrome/browser/ash/login/screens/choobe_screen_browsertest.cc b/chrome/browser/ash/login/screens/choobe_screen_browsertest.cc
index 5867fc72..22b6c0f 100644
--- a/chrome/browser/ash/login/screens/choobe_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/choobe_screen_browsertest.cc
@@ -71,8 +71,8 @@
 
     test::SetFakeTouchpadDevice();
     original_callback_ = choobe_screen->get_exit_callback_for_testing();
-    choobe_screen->set_exit_callback_for_testing(base::BindRepeating(
-        &ChoobeScreenTest::HandleScreenExit, base::Unretained(this)));
+    choobe_screen->set_exit_callback_for_testing(
+        screen_result_waiter_.GetRepeatingCallback());
     OobeBaseTest::SetUpOnMainThread();
   }
 
@@ -83,32 +83,19 @@
         ChoobeScreenView::kScreenId);
   }
 
-  void WaitForScreenExit() {
-    if (result_.has_value()) {
-      return;
-    }
-    base::test::TestFuture<void> waiter;
-    quit_closure_ = waiter.GetCallback();
-    EXPECT_TRUE(waiter.Wait());
+  ChoobeScreen::Result WaitForScreenExitResult() {
+    ChoobeScreen::Result result = screen_result_waiter_.Take();
+    original_callback_.Run(result);
+    return result;
   }
 
-  ChoobeScreen::ScreenExitCallback original_callback_;
-  absl::optional<ChoobeScreen::Result> result_;
-
  protected:
   base::test::ScopedFeatureList feature_list_;
   LoginManagerMixin login_manager_mixin_{&mixin_host_};
 
  private:
-  void HandleScreenExit(ChoobeScreen::Result result) {
-    result_ = result;
-    original_callback_.Run(result);
-    if (quit_closure_) {
-      std::move(quit_closure_).Run();
-    }
-  }
-
-  base::OnceClosure quit_closure_;
+  base::test::TestFuture<ChoobeScreen::Result> screen_result_waiter_;
+  ChoobeScreen::ScreenExitCallback original_callback_;
 };
 
 IN_PROC_BROWSER_TEST_F(ChoobeScreenTest, Next) {
@@ -124,9 +111,9 @@
   // Check Next Button not disabled
   test::OobeJS().ExpectEnabledPath(kNextButtonPath);
   test::OobeJS().ClickOnPath(kNextButtonPath);
-  WaitForScreenExit();
+  ChoobeScreen::Result result = WaitForScreenExitResult();
 
-  EXPECT_EQ(result_.value(), ChoobeScreen::Result::SELECTED);
+  EXPECT_EQ(result, ChoobeScreen::Result::SELECTED);
 }
 
 IN_PROC_BROWSER_TEST_F(ChoobeScreenTest, Skip) {
@@ -135,9 +122,9 @@
   test::OobeJS().ExpectVisiblePath(kDialogPath);
   test::OobeJS().ExpectDisabledPath(kNextButtonPath);
   test::OobeJS().ClickOnPath(kSkipButtonPath);
-  WaitForScreenExit();
+  ChoobeScreen::Result result = WaitForScreenExitResult();
 
-  EXPECT_EQ(result_.value(), ChoobeScreen::Result::SKIPPED);
+  EXPECT_EQ(result, ChoobeScreen::Result::SKIPPED);
 }
 
 class ChoobeScreenDisabledScreenTest : public ChoobeScreenTest {
@@ -152,9 +139,9 @@
 
 IN_PROC_BROWSER_TEST_F(ChoobeScreenDisabledScreenTest, NotEnoughScreen) {
   ShowChoobeScreen();
-  WaitForScreenExit();
+  ChoobeScreen::Result result = WaitForScreenExitResult();
 
-  EXPECT_EQ(result_.value(), ChoobeScreen::Result::NOT_APPLICABLE);
+  EXPECT_EQ(result, ChoobeScreen::Result::NOT_APPLICABLE);
 }
 
 class ChoobeScreenTestWithParams
@@ -215,8 +202,8 @@
   if (is_theme_selection_selected || is_touchpad_scroll_selected ||
       is_display_size_selected) {
     test::OobeJS().TapOnPath(kNextButtonPath);
-    WaitForScreenExit();
-    EXPECT_EQ(result_.value(), ChoobeScreen::Result::SELECTED);
+    ChoobeScreen::Result result = WaitForScreenExitResult();
+    EXPECT_EQ(result, ChoobeScreen::Result::SELECTED);
 
     EXPECT_EQ(WizardController::default_controller()
                   ->choobe_flow_controller()
@@ -239,9 +226,9 @@
   } else {
     test::OobeJS().ExpectDisabledPath(kNextButtonPath);
     test::OobeJS().ClickOnPath(kSkipButtonPath);
-    WaitForScreenExit();
+    ChoobeScreen::Result result = WaitForScreenExitResult();
 
-    EXPECT_EQ(result_.value(), ChoobeScreen::Result::SKIPPED);
+    EXPECT_EQ(result, ChoobeScreen::Result::SKIPPED);
     PrefService* prefs = ProfileManager::GetActiveUserProfile()->GetPrefs();
     EXPECT_FALSE(prefs->HasPrefPath(prefs::kChoobeSelectedScreens));
   }
diff --git a/chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.cc b/chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.cc
index 18dcc2f..f262cee 100644
--- a/chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.cc
+++ b/chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.cc
@@ -7,15 +7,13 @@
 #include <memory>
 #include <utility>
 
-#include "ash/constants/ash_features.h"
-#include "base/check_op.h"
+#include "base/check.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
-#include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
-#include "chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h"
 #include "chrome/browser/ash/login/screens/base_screen.h"
 #include "chrome/browser/ash/login/wizard_context.h"
-#include "chrome/browser/profiles/profile_manager.h"
+#include "chromeos/ash/components/cryptohome/auth_factor.h"
+#include "chromeos/ash/components/login/auth/public/user_context.h"
 #include "chromeos/ash/components/osauth/public/auth_session_storage.h"
 
 namespace ash {
@@ -27,79 +25,21 @@
 BaseOSAuthSetupScreen::~BaseOSAuthSetupScreen() = default;
 
 void BaseOSAuthSetupScreen::HideImpl() {
-  if (!ash::features::ShouldUseAuthSessionStorage()) {
-    StoreQuickUnlockContext();
-  }
   session_refresher_.reset();
 }
 
-void BaseOSAuthSetupScreen::EnsureQuickUnlockToken() {
-  CHECK(!ash::features::ShouldUseAuthSessionStorage());
-  if (quick_unlock_token_) {
-    return;
-  }
-  CHECK(context()->extra_factors_auth_session);
-
-  quick_unlock::QuickUnlockStorage* quick_unlock_storage =
-      quick_unlock::QuickUnlockFactory::GetForProfile(
-          ProfileManager::GetActiveUserProfile());
-  CHECK(quick_unlock_storage);
-  quick_unlock_token_ = quick_unlock_storage->CreateAuthToken(
-      *context()->extra_factors_auth_session);
-}
-
-void BaseOSAuthSetupScreen::StoreQuickUnlockContext() {
-  CHECK(!ash::features::ShouldUseAuthSessionStorage());
-  if (!quick_unlock_token_) {
-    return;
-  }
-  quick_unlock::QuickUnlockStorage* quick_unlock_storage =
-      quick_unlock::QuickUnlockFactory::GetForProfile(
-          ProfileManager::GetActiveUserProfile());
-  if (!quick_unlock_storage->GetUserContext(*quick_unlock_token_)) {
-    // Context already expired
-    quick_unlock_token_ = absl::nullopt;
-    return;
-  }
-  context()->extra_factors_auth_session = std::make_unique<UserContext>(
-      *quick_unlock_storage->GetUserContext(*quick_unlock_token_));
-  quick_unlock_storage->ReplaceUserContext(*quick_unlock_token_,
-                                           std::make_unique<UserContext>());
-  quick_unlock_token_ = absl::nullopt;
-}
-
 AuthProofToken BaseOSAuthSetupScreen::GetToken() {
-  if (!ash::features::ShouldUseAuthSessionStorage()) {
-    EnsureQuickUnlockToken();
-    return *quick_unlock_token_;
-  }
   CHECK(context()->extra_factors_token.has_value());
   return *(context()->extra_factors_token);
 }
 
 void BaseOSAuthSetupScreen::KeepAliveAuthSession() {
-  if (!ash::features::ShouldUseAuthSessionStorage()) {
-    return;
-  }
   session_refresher_ = AuthSessionStorage::Get()->KeepAlive(GetToken());
 }
 
 void BaseOSAuthSetupScreen::InspectContextAndContinue(
     InspectContextCallback inspect_callback,
     base::OnceClosure continuation) {
-  if (!ash::features::ShouldUseAuthSessionStorage()) {
-    EnsureQuickUnlockToken();
-    quick_unlock::QuickUnlockStorage* quick_unlock_storage =
-        quick_unlock::QuickUnlockFactory::GetForProfile(
-            ProfileManager::GetActiveUserProfile());
-    UserContext* context =
-        quick_unlock_storage->GetUserContext(*quick_unlock_token_);
-    std::move(inspect_callback).Run(context);
-    if (context) {
-      std::move(continuation).Run();
-    }
-    return;
-  }
   if (!context()->extra_factors_token.has_value()) {
     std::move(inspect_callback).Run(nullptr);
     return;
diff --git a/chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.h b/chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.h
index 39b7872..ecd54f2 100644
--- a/chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.h
+++ b/chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.h
@@ -33,9 +33,6 @@
   ~BaseOSAuthSetupScreen() override;
 
  protected:
-  // In case when QuickUnlockStorage is used, would ensure
-  // that any modifications to UserContext happened while screen
-  // was show are correctly stored in WizardContext.
   // Resets AuthSession refresher if it was requested.
   void HideImpl() override;
 
@@ -61,13 +58,11 @@
   void EstablishKnowledgeFactorGuard(base::OnceClosure continuation);
 
  private:
-  void EnsureQuickUnlockToken();
   void InspectContextAndContinueWithContext(
       InspectContextCallback inspect_callback,
       base::OnceClosure continuation,
       std::unique_ptr<UserContext> user_context);
 
-  void StoreQuickUnlockContext();
   void CheckForKnowledgeFactorPresence(base::OnceClosure continuation,
                                        UserContext* context);
 
diff --git a/chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen.cc b/chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen.cc
index e74eb73..e2a7645 100644
--- a/chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen.cc
+++ b/chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen.cc
@@ -96,19 +96,8 @@
   weak_ptr_factory_.InvalidateWeakPtrs();
 
   std::string token;
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    CHECK(context()->extra_factors_token.has_value());
-    token = context()->extra_factors_token.value();
-  } else {
-    CHECK(context()->extra_factors_auth_session);
-
-    quick_unlock::QuickUnlockStorage* quick_unlock_storage =
-        quick_unlock::QuickUnlockFactory::GetForProfile(
-            ProfileManager::GetActiveUserProfile());
-    CHECK(quick_unlock_storage);
-    token = quick_unlock_storage->CreateAuthToken(
-        *context()->extra_factors_auth_session);
-  }
+  CHECK(context()->extra_factors_token.has_value());
+  token = context()->extra_factors_token.value();
   auto& recovery_editor = auth::GetRecoveryFactorEditor(
       quick_unlock::QuickUnlockFactory::GetDelegate(),
       g_browser_process->local_state());
@@ -122,22 +111,14 @@
     WizardContext& wizard_context,
     CryptohomeRecoverySetupScreen::Result result) {
   // Clear the auth session if it's not needed for PIN setup.
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (wizard_context.extra_factors_token.has_value()) {
-      auto& token = wizard_context.extra_factors_token.value();
-      auto* storage = ash::AuthSessionStorage::Get();
-      if (storage->IsValid(token) &&
-          cryptohome_pin_engine_.ShouldSkipSetupBecauseOfPolicy(
-              storage->Peek(token)->GetAccountId())) {
-        storage->Invalidate(token, base::DoNothing());
-        wizard_context.extra_factors_token = absl::nullopt;
-      }
-    }
-  } else {
-    if (wizard_context.extra_factors_auth_session != nullptr &&
+  if (wizard_context.extra_factors_token.has_value()) {
+    auto& token = wizard_context.extra_factors_token.value();
+    auto* storage = ash::AuthSessionStorage::Get();
+    if (storage->IsValid(token) &&
         cryptohome_pin_engine_.ShouldSkipSetupBecauseOfPolicy(
-            wizard_context.extra_factors_auth_session->GetAccountId())) {
-      wizard_context.extra_factors_auth_session.reset();
+            storage->Peek(token)->GetAccountId())) {
+      storage->Invalidate(token, base::DoNothing());
+      wizard_context.extra_factors_token = absl::nullopt;
     }
   }
 
diff --git a/chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen_browsertest.cc b/chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen_browsertest.cc
index ce12e8a..28e2aef 100644
--- a/chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen_browsertest.cc
@@ -66,22 +66,17 @@
     // Wait for the recovery screen and copy the user context before it is
     // cleared.
     WaitForScreenExit();
-    std::unique_ptr<UserContext> user_context;
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      user_context = ash::AuthSessionStorage::Get()->BorrowForTests(
-          FROM_HERE, context->extra_factors_token.value());
-      context->extra_factors_token = absl::nullopt;
-    } else {
-      user_context =
-          std::make_unique<UserContext>(*context->extra_factors_auth_session);
-    }
+    std::unique_ptr<UserContext> user_context =
+        ash::AuthSessionStorage::Get()->BorrowForTests(
+            FROM_HERE, context->extra_factors_token.value());
+    context->extra_factors_token = absl::nullopt;
     cryptohome_.MarkUserAsExisting(user_context->GetAccountId());
     ContinueScreenExit();
     // Wait until the OOBE flow finishes before we set new values on the wizard
     // context.
     OobeScreenExitWaiter(UserCreationView::kScreenId).Wait();
 
-    // Set the values on the wizard context: the `extra_factors_auth_session`
+    // Set the values on the wizard context: the `extra_factors_token`
     // is available after the previous screens have run regularly, and it holds
     // an authenticated auth session.
     user_context->ResetAuthSessionIds();
@@ -90,12 +85,8 @@
     user_context->SetAuthSessionIds(session_ids.first, session_ids.second);
     user_context->SetSessionLifetime(base::Time::Now() +
                                      cryptohome::kAuthsessionInitialLifetime);
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      context->extra_factors_token =
-          ash::AuthSessionStorage::Get()->Store(std::move(user_context));
-    } else {
-      context->extra_factors_auth_session = std::move(user_context);
-    }
+    context->extra_factors_token =
+        ash::AuthSessionStorage::Get()->Store(std::move(user_context));
     context->skip_post_login_screens_for_tests = false;
     // Clear the test state.
     result_ = absl::nullopt;
@@ -166,7 +157,7 @@
 }
 
 // If user opts in to recovery, the screen should be shown. In this case
-// `extra_factors_auth_session` should not be cleared.
+// auth session should not be cleared.
 IN_PROC_BROWSER_TEST_F(CryptohomeRecoverySetupScreenTest,
                        ShowDoesntClearAuthSession) {
   LoginAsRegularUser();
@@ -179,16 +170,9 @@
   base::HistogramTester histogram_tester;
 
   ShowScreen();
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    EXPECT_TRUE(LoginDisplayHost::default_host()
-                    ->GetWizardContextForTesting()
-                    ->extra_factors_token.has_value());
-  } else {
-    EXPECT_NE(LoginDisplayHost::default_host()
+  EXPECT_TRUE(LoginDisplayHost::default_host()
                   ->GetWizardContextForTesting()
-                  ->extra_factors_auth_session,
-              nullptr);
-  }
+                  ->extra_factors_token.has_value());
 
   ContinueScreenExit();
   EXPECT_EQ(result_.value(), CryptohomeRecoverySetupScreen::Result::DONE);
@@ -200,7 +184,7 @@
 
 // If user opts in to recovery, the screen should be shown.
 // The PIN setup screen is skipped due to policy. In this case
-// `extra_factors_auth_session` should be cleared.
+// auth session should be cleared.
 IN_PROC_BROWSER_TEST_F(CryptohomeRecoverySetupScreenTest,
                        ShowClearsAuthSession) {
   LoginAsRegularUser();
@@ -216,16 +200,9 @@
   base::HistogramTester histogram_tester;
 
   ShowScreen();
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    EXPECT_FALSE(LoginDisplayHost::default_host()
-                     ->GetWizardContextForTesting()
-                     ->extra_factors_token.has_value());
-  } else {
-    EXPECT_EQ(LoginDisplayHost::default_host()
-                  ->GetWizardContextForTesting()
-                  ->extra_factors_auth_session,
-              nullptr);
-  }
+  EXPECT_FALSE(LoginDisplayHost::default_host()
+                   ->GetWizardContextForTesting()
+                   ->extra_factors_token.has_value());
 
   ContinueScreenExit();
   EXPECT_EQ(result_.value(), CryptohomeRecoverySetupScreen::Result::DONE);
diff --git a/chrome/browser/ash/login/screens/osauth/osauth_error_screen.cc b/chrome/browser/ash/login/screens/osauth/osauth_error_screen.cc
index 4c95cca..45975bb 100644
--- a/chrome/browser/ash/login/screens/osauth/osauth_error_screen.cc
+++ b/chrome/browser/ash/login/screens/osauth/osauth_error_screen.cc
@@ -53,13 +53,11 @@
   CHECK_GE(args.size(), 1u);
   const std::string& action_id = args[0].GetString();
   if (action_id == kUserActionRetry) {
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      if (context()->extra_factors_token.has_value()) {
-        AuthSessionStorage::Get()->Invalidate(
-            GetToken(), base::BindOnce(&OSAuthErrorScreen::OnTokenInvalidated,
-                                       weak_ptr_factory_.GetWeakPtr()));
-        return;
-      }
+    if (context()->extra_factors_token.has_value()) {
+      AuthSessionStorage::Get()->Invalidate(
+          GetToken(), base::BindOnce(&OSAuthErrorScreen::OnTokenInvalidated,
+                                     weak_ptr_factory_.GetWeakPtr()));
+      return;
     }
     exit_callback_.Run(Result::kAbortSignin);
     return;
diff --git a/chrome/browser/ash/login/screens/osauth/password_selection_screen_browsertest.cc b/chrome/browser/ash/login/screens/osauth/password_selection_screen_browsertest.cc
new file mode 100644
index 0000000..4967aa0
--- /dev/null
+++ b/chrome/browser/ash/login/screens/osauth/password_selection_screen_browsertest.cc
@@ -0,0 +1,119 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/login/screens/osauth/password_selection_screen.h"
+
+#include "ash/constants/ash_features.h"
+#include "chrome/browser/ash/login/test/oobe_base_test.h"
+#include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
+#include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
+#include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/policy/profile_policy_connector.h"
+#include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h"
+#include "chrome/browser/ui/webui/ash/login/password_selection_screen_handler.h"
+#include "chrome/test/base/fake_gaia_mixin.h"
+#include "content/public/test/browser_test.h"
+
+namespace ash {
+
+namespace {
+
+const test::UIPath kGaiaPasswordButton = {"password-selection",
+                                          "gaiaPasswordButton"};
+const test::UIPath kLocalPasswordButton = {"password-selection",
+                                           "localPasswordButton"};
+const test::UIPath kNextButton = {"password-selection", "nextButton"};
+
+}  // namespace
+
+class PasswordSelectionScreenTest : public OobeBaseTest {
+ public:
+  PasswordSelectionScreenTest() {
+    feature_list_.InitAndEnableFeature(features::kLocalPasswordForConsumers);
+  }
+  ~PasswordSelectionScreenTest() override = default;
+
+  void SetUpOnMainThread() override {
+    original_callback_ = GetScreen()->get_exit_callback_for_testing();
+    GetScreen()->set_exit_callback_for_testing(
+        base::BindRepeating(&PasswordSelectionScreenTest::HandleScreenExit,
+                            base::Unretained(this)));
+    OobeBaseTest::SetUpOnMainThread();
+  }
+
+  void TearDownOnMainThread() override {
+    OobeBaseTest::TearDownOnMainThread();
+    result_ = absl::nullopt;
+  }
+
+  PasswordSelectionScreen* GetScreen() {
+    return static_cast<PasswordSelectionScreen*>(
+        WizardController::default_controller()->screen_manager()->GetScreen(
+            PasswordSelectionScreenView::kScreenId));
+  }
+
+  void WaitForScreenExit() {
+    if (result_.has_value()) {
+      return;
+    }
+
+    base::RunLoop run_loop;
+    screen_exit_callback_ = run_loop.QuitClosure();
+    run_loop.Run();
+
+    original_callback_.Run(result_.value());
+  }
+
+  absl::optional<PasswordSelectionScreen::Result> result_;
+
+ private:
+  void HandleScreenExit(PasswordSelectionScreen::Result result) {
+    result_ = result;
+    if (screen_exit_callback_) {
+      std::move(screen_exit_callback_).Run();
+    }
+  }
+
+  base::test::ScopedFeatureList feature_list_;
+  FakeGaiaMixin fake_gaia_{&mixin_host_};
+  PasswordSelectionScreen::ScreenExitCallback original_callback_;
+  base::RepeatingClosure screen_exit_callback_;
+};
+
+IN_PROC_BROWSER_TEST_F(PasswordSelectionScreenTest, GaiaPasswordChoice) {
+  LoginDisplayHost::default_host()
+      ->GetOobeUI()
+      ->GetView<GaiaScreenHandler>()
+      ->ShowSigninScreenForTest(FakeGaiaMixin::kFakeUserEmail,
+                                FakeGaiaMixin::kFakeUserPassword,
+                                FakeGaiaMixin::kEmptyUserServices);
+  OobeScreenWaiter(PasswordSelectionScreenView::kScreenId).Wait();
+  test::OobeJS().ExpectVisiblePath(kGaiaPasswordButton);
+  test::OobeJS().ClickOnPath(kGaiaPasswordButton);
+  test::OobeJS().ClickOnPath(kNextButton);
+  WaitForScreenExit();
+  EXPECT_EQ(result_.value(),
+            PasswordSelectionScreen::Result::GAIA_PASSWORD_CHOICE);
+}
+
+IN_PROC_BROWSER_TEST_F(PasswordSelectionScreenTest, LocalPasswordChoice) {
+  LoginDisplayHost::default_host()
+      ->GetOobeUI()
+      ->GetView<GaiaScreenHandler>()
+      ->ShowSigninScreenForTest(FakeGaiaMixin::kFakeUserEmail,
+                                FakeGaiaMixin::kFakeUserPassword,
+                                FakeGaiaMixin::kEmptyUserServices);
+  OobeScreenWaiter(PasswordSelectionScreenView::kScreenId).Wait();
+  test::OobeJS().ExpectVisiblePath(kLocalPasswordButton);
+  test::OobeJS().ClickOnPath(kLocalPasswordButton);
+  test::OobeJS().ClickOnPath(kNextButton);
+  WaitForScreenExit();
+  EXPECT_EQ(result_.value(),
+            PasswordSelectionScreen::Result::LOCAL_PASSWORD_CHOICE);
+  EXPECT_FALSE(LoginDisplayHost::default_host()
+                   ->GetWizardContextForTesting()
+                   ->knowledge_factor_setup.local_password_forced);
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/login/screens/osauth/recovery_eligibility_screen_browsertest.cc b/chrome/browser/ash/login/screens/osauth/recovery_eligibility_screen_browsertest.cc
index 771509ad..893014b 100644
--- a/chrome/browser/ash/login/screens/osauth/recovery_eligibility_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/osauth/recovery_eligibility_screen_browsertest.cc
@@ -76,21 +76,16 @@
     WaitForScreenExit();
 
     std::unique_ptr<UserContext> user_context;
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      user_context = ash::AuthSessionStorage::Get()->BorrowForTests(
-          FROM_HERE, context->extra_factors_token.value());
-      context->extra_factors_token = absl::nullopt;
-    } else {
-      user_context =
-          std::make_unique<UserContext>(*context->extra_factors_auth_session);
-    }
+    user_context = ash::AuthSessionStorage::Get()->BorrowForTests(
+        FROM_HERE, context->extra_factors_token.value());
+    context->extra_factors_token = absl::nullopt;
     cryptohome_.MarkUserAsExisting(user_context->GetAccountId());
     ContinueScreenExit();
     // Wait until the OOBE flow finishes before we set new values on the wizard
     // context.
     OobeScreenExitWaiter(UserCreationView::kScreenId).Wait();
 
-    // Set the values on the wizard context: the `extra_factors_auth_session`
+    // Set the values on the wizard context: the `extra_factors_token`
     // is available after the previous screens have run regularly, and it holds
     // an authenticated auth session.
     user_context->ResetAuthSessionIds();
@@ -99,12 +94,8 @@
     user_context->SetAuthSessionIds(session_ids.first, session_ids.second);
     user_context->SetSessionLifetime(base::Time::Now() +
                                      cryptohome::kAuthsessionInitialLifetime);
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      context->extra_factors_token =
-          ash::AuthSessionStorage::Get()->Store(std::move(user_context));
-    } else {
-      context->extra_factors_auth_session = std::move(user_context);
-    }
+    context->extra_factors_token =
+        ash::AuthSessionStorage::Get()->Store(std::move(user_context));
     context->skip_post_login_screens_for_tests = false;
     result_ = absl::nullopt;
   }
diff --git a/chrome/browser/ash/login/screens/pin_setup_screen.cc b/chrome/browser/ash/login/screens/pin_setup_screen.cc
index 313eed72..7b8c6ef 100644
--- a/chrome/browser/ash/login/screens/pin_setup_screen.cc
+++ b/chrome/browser/ash/login/screens/pin_setup_screen.cc
@@ -101,25 +101,16 @@
 PinSetupScreen::~PinSetupScreen() = default;
 
 bool PinSetupScreen::ShouldBeSkipped(const WizardContext& context) const {
-  // Just a precaution:
-  AccountId account_id;
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (!context.extra_factors_token.has_value()) {
-      return true;
-    }
-    if (!ash::AuthSessionStorage::Get()->IsValid(
-            context.extra_factors_token.value())) {
-      return true;
-    }
-    account_id = ash::AuthSessionStorage::Get()
-                     ->Peek(context.extra_factors_token.value())
-                     ->GetAccountId();
-  } else {
-    if (!context.extra_factors_auth_session) {
-      return true;
-    }
-    account_id = context.extra_factors_auth_session->GetAccountId();
+  if (!context.extra_factors_token.has_value()) {
+    return true;
   }
+  if (!ash::AuthSessionStorage::Get()->IsValid(
+          context.extra_factors_token.value())) {
+    return true;
+  }
+  AccountId account_id = ash::AuthSessionStorage::Get()
+                             ->Peek(context.extra_factors_token.value())
+                             ->GetAccountId();
   if (context.skip_post_login_screens_for_tests ||
       cryptohome_pin_engine_.ShouldSkipSetupBecauseOfPolicy(account_id)) {
     return true;
@@ -163,21 +154,8 @@
           ProfileManager::GetActiveUserProfile());
   quick_unlock_storage->MarkStrongAuth();
   std::string token;
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    CHECK(context()->extra_factors_token);
-    token = *context()->extra_factors_token;
-  } else {
-    std::unique_ptr<UserContext> user_context =
-        std::move(context()->extra_factors_auth_session);
-
-    // Due to crbug.com/1203420 we need to mark the key as a wildcard (no
-    // label).
-    if (user_context->GetKey()->GetLabel() == kCryptohomeGaiaKeyLabel) {
-      user_context->GetKey()->SetLabel(kCryptohomeWildcardLabel);
-    }
-
-    token = quick_unlock_storage->CreateAuthToken(*user_context);
-  }
+  CHECK(context()->extra_factors_token);
+  token = *context()->extra_factors_token;
   bool is_child_account =
       user_manager::UserManager::Get()->IsLoggedInAsChildUser();
 
@@ -212,14 +190,10 @@
 }
 
 void PinSetupScreen::ClearAuthData(WizardContext& context) {
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (context.extra_factors_token.has_value()) {
-      ash::AuthSessionStorage::Get()->Invalidate(
-          context.extra_factors_token.value(), base::DoNothing());
-      context.extra_factors_token = absl::nullopt;
-    }
-  } else {
-    context.extra_factors_auth_session.reset();
+  if (context.extra_factors_token.has_value()) {
+    ash::AuthSessionStorage::Get()->Invalidate(
+        context.extra_factors_token.value(), base::DoNothing());
+    context.extra_factors_token = absl::nullopt;
   }
 }
 
diff --git a/chrome/browser/ash/login/screens/pin_setup_screen_browsertest.cc b/chrome/browser/ash/login/screens/pin_setup_screen_browsertest.cc
index c62cfed9..c4177ae 100644
--- a/chrome/browser/ash/login/screens/pin_setup_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/pin_setup_screen_browsertest.cc
@@ -88,13 +88,9 @@
     // Add an authenticated session to the user context used during OOBE. In
     // production, this is set by earlier screens which are skipped in this
     // test.
-    std::unique_ptr<UserContext> context;
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      context = ash::AuthSessionStorage::Get()->BorrowForTests(
-          FROM_HERE, wizard_context->extra_factors_token.value());
-    } else {
-      context = std::move(wizard_context->extra_factors_auth_session);
-    }
+    std::unique_ptr<UserContext> context =
+        ash::AuthSessionStorage::Get()->BorrowForTests(
+            FROM_HERE, wizard_context->extra_factors_token.value());
     // LoginManagerMixin uses StubAuthenticator that fills out authsession.
     // Reset Authsession to correctly interact with FakeUserDataAuthClient.
     context->ResetAuthSessionIds();
@@ -104,14 +100,8 @@
     context->SetAuthSessionIds(session_ids.first, session_ids.second);
     context->SetSessionLifetime(base::Time::Now() +
                                 cryptohome::kAuthsessionInitialLifetime);
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      ash::AuthSessionStorage::Get()->Return(
-          wizard_context->extra_factors_token.value(), std::move(context));
-    } else {
-      LoginDisplayHost::default_host()
-          ->GetWizardContext()
-          ->extra_factors_auth_session = std::move(context);
-    }
+    ash::AuthSessionStorage::Get()->Return(
+        wizard_context->extra_factors_token.value(), std::move(context));
   }
 
   PinSetupScreen* GetScreen() {
@@ -143,33 +133,20 @@
   }
 
   void ConfigureUserContextForTest() {
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      std::unique_ptr<UserContext> context = std::make_unique<UserContext>();
-      context->SetAuthSessionIds("fake-session-id", "broadcast");
-      context->SetSessionLifetime(base::Time::Now() +
-                                  cryptohome::kAuthsessionInitialLifetime);
-      LoginDisplayHost::default_host()
-          ->GetWizardContextForTesting()
-          ->extra_factors_token =
-          ash::AuthSessionStorage::Get()->Store(std::move(context));
-    } else {
-      LoginDisplayHost::default_host()
-          ->GetWizardContextForTesting()
-          ->extra_factors_auth_session = std::make_unique<UserContext>();
-    }
+    std::unique_ptr<UserContext> context = std::make_unique<UserContext>();
+    context->SetAuthSessionIds("fake-session-id", "broadcast");
+    context->SetSessionLifetime(base::Time::Now() +
+                                cryptohome::kAuthsessionInitialLifetime);
+    LoginDisplayHost::default_host()
+        ->GetWizardContextForTesting()
+        ->extra_factors_token =
+        ash::AuthSessionStorage::Get()->Store(std::move(context));
   }
 
   void CheckCredentialsWereCleared() {
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      EXPECT_FALSE(LoginDisplayHost::default_host()
-                       ->GetWizardContextForTesting()
-                       ->extra_factors_token.has_value());
-    } else {
-      EXPECT_EQ(LoginDisplayHost::default_host()
-                    ->GetWizardContextForTesting()
-                    ->extra_factors_auth_session,
-                nullptr);
-    }
+    EXPECT_FALSE(LoginDisplayHost::default_host()
+                     ->GetWizardContextForTesting()
+                     ->extra_factors_token.has_value());
   }
 
   absl::optional<PinSetupScreen::Result> screen_result_;
@@ -204,7 +181,7 @@
   histogram_tester_.ExpectTotalCount("OOBE.StepCompletionTime.Pin-setup", 0);
 }
 
-// If the PIN setup screen is skipped, `extra_factors_auth_session` should be
+// If the PIN setup screen is skipped, auth session should be
 // cleared.
 IN_PROC_BROWSER_TEST_F(PinSetupScreenTest, SkippedClearsAuthSession) {
   ConfigureUserContextForTest();
@@ -226,7 +203,7 @@
   EXPECT_EQ(screen_result_.value(), PinSetupScreen::Result::USER_SKIP);
 }
 
-// If the PIN setup screen is shown, `extra_factors_auth_session` should be
+// If the PIN setup screen is shown, auth session should be
 // cleared.
 IN_PROC_BROWSER_TEST_F(PinSetupScreenTest, ShowClearsAuthSession) {
   ConfigureUserContextForTest();
diff --git a/chrome/browser/ash/login/ui/login_display_host_common.cc b/chrome/browser/ash/login/ui/login_display_host_common.cc
index 3dc7773..15595e1 100644
--- a/chrome/browser/ash/login/ui/login_display_host_common.cc
+++ b/chrome/browser/ash/login/ui/login_display_host_common.cc
@@ -586,24 +586,15 @@
       RecoveryEligibilityScreen::ShouldSkipRecoverySetupBecauseOfPolicy()) {
     return;
   }
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    wizard_context_->extra_factors_token = AuthSessionStorage::Get()->Store(
-        std::make_unique<UserContext>(user_context));
-  } else {
-    wizard_context_->extra_factors_auth_session =
-        std::make_unique<UserContext>(user_context);
-  }
+  wizard_context_->extra_factors_token = AuthSessionStorage::Get()->Store(
+      std::make_unique<UserContext>(user_context));
 }
 
 void LoginDisplayHostCommon::ClearOnboardingAuthSession() {
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (wizard_context_->extra_factors_token.has_value()) {
-      AuthSessionStorage::Get()->Invalidate(
-          wizard_context_->extra_factors_token.value(), base::DoNothing());
-      wizard_context_->extra_factors_token = absl::nullopt;
-    }
-  } else {
-    wizard_context_->extra_factors_auth_session.reset();
+  if (wizard_context_->extra_factors_token.has_value()) {
+    AuthSessionStorage::Get()->Invalidate(
+        wizard_context_->extra_factors_token.value(), base::DoNothing());
+    wizard_context_->extra_factors_token = absl::nullopt;
   }
 }
 
diff --git a/chrome/browser/ash/login/webview_login_browsertest.cc b/chrome/browser/ash/login/webview_login_browsertest.cc
index 09761e85..7bb47057 100644
--- a/chrome/browser/ash/login/webview_login_browsertest.cc
+++ b/chrome/browser/ash/login/webview_login_browsertest.cc
@@ -958,22 +958,15 @@
   test::OobeJS().ClickOnPath(kPrimaryButton);
   OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
 
-  const UserContext* user_context = nullptr;
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    CHECK(LoginDisplayHost::default_host()
-              ->GetWizardContext()
-              ->extra_factors_token.has_value());
-    auto* storage = ash::AuthSessionStorage::Get();
-    auto& token = LoginDisplayHost::default_host()
-                      ->GetWizardContext()
-                      ->extra_factors_token.value();
-    CHECK(storage->IsValid(token));
-    user_context = storage->Peek(token);
-  } else {
-    user_context = LoginDisplayHost::default_host()
-                       ->GetWizardContext()
-                       ->extra_factors_auth_session.get();
-  }
+  CHECK(LoginDisplayHost::default_host()
+            ->GetWizardContext()
+            ->extra_factors_token.has_value());
+  auto* storage = ash::AuthSessionStorage::Get();
+  auto& token = LoginDisplayHost::default_host()
+                    ->GetWizardContext()
+                    ->extra_factors_token.value();
+  CHECK(storage->IsValid(token));
+  const UserContext* user_context = storage->Peek(token);
 
   EXPECT_EQ(user_context->GetReauthProofToken(), "fake-reauth-proof-token");
 }
@@ -994,22 +987,15 @@
   test::OobeJS().ClickOnPath(kPrimaryButton);
   OobeScreenExitWaiter(GaiaView::kScreenId).Wait();
 
-  const UserContext* user_context = nullptr;
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    CHECK(LoginDisplayHost::default_host()
-              ->GetWizardContext()
-              ->extra_factors_token.has_value());
-    auto* storage = ash::AuthSessionStorage::Get();
-    auto& token = LoginDisplayHost::default_host()
-                      ->GetWizardContext()
-                      ->extra_factors_token.value();
-    CHECK(storage->IsValid(token));
-    user_context = storage->Peek(token);
-  } else {
-    user_context = LoginDisplayHost::default_host()
-                       ->GetWizardContext()
-                       ->extra_factors_auth_session.get();
-  }
+  CHECK(LoginDisplayHost::default_host()
+            ->GetWizardContext()
+            ->extra_factors_token.has_value());
+  auto* storage = ash::AuthSessionStorage::Get();
+  auto& token = LoginDisplayHost::default_host()
+                    ->GetWizardContext()
+                    ->extra_factors_token.value();
+  CHECK(storage->IsValid(token));
+  const UserContext* user_context = storage->Peek(token);
   EXPECT_TRUE(user_context->GetReauthProofToken().empty());
 }
 
diff --git a/chrome/browser/ash/login/wizard_context.h b/chrome/browser/ash/login/wizard_context.h
index 2e3a011..9bcfefc9 100644
--- a/chrome/browser/ash/login/wizard_context.h
+++ b/chrome/browser/ash/login/wizard_context.h
@@ -164,11 +164,6 @@
 
   KnowledgeFactorSetup knowledge_factor_setup;
 
-  // Authorization data that is required by PinSetup screen to add PIN as
-  // another possible auth factor. Can be empty (if PIN is not supported).
-  // In future will be replaced by AuthSession.
-  std::unique_ptr<UserContext> extra_factors_auth_session;
-
   // Same as above, but the actual context is stored in AuthSessionStorage,
   // and the token can be used to retrieve it.
   absl::optional<AuthProofToken> extra_factors_token;
diff --git a/chrome/browser/ash/multidevice_setup/auth_token_validator_factory.cc b/chrome/browser/ash/multidevice_setup/auth_token_validator_factory.cc
index da61708f5..7d82af80 100644
--- a/chrome/browser/ash/multidevice_setup/auth_token_validator_factory.cc
+++ b/chrome/browser/ash/multidevice_setup/auth_token_validator_factory.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/ash/multidevice_setup/auth_token_validator_factory.h"
 
-#include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
 #include "chrome/browser/ash/multidevice_setup/auth_token_validator_impl.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -40,9 +39,7 @@
 std::unique_ptr<KeyedService>
 AuthTokenValidatorFactory::BuildServiceInstanceForBrowserContext(
     content::BrowserContext* context) const {
-  return std::make_unique<AuthTokenValidatorImpl>(
-      quick_unlock::QuickUnlockFactory::GetForProfile(
-          Profile::FromBrowserContext(context)));
+  return std::make_unique<AuthTokenValidatorImpl>();
 }
 
 }  // namespace multidevice_setup
diff --git a/chrome/browser/ash/multidevice_setup/auth_token_validator_impl.cc b/chrome/browser/ash/multidevice_setup/auth_token_validator_impl.cc
index a20eb9295..d698c0f0 100644
--- a/chrome/browser/ash/multidevice_setup/auth_token_validator_impl.cc
+++ b/chrome/browser/ash/multidevice_setup/auth_token_validator_impl.cc
@@ -4,32 +4,18 @@
 
 #include "chrome/browser/ash/multidevice_setup/auth_token_validator_impl.h"
 
-#include "ash/constants/ash_features.h"
 #include "chrome/browser/ash/login/quick_unlock/auth_token.h"
-#include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
-#include "chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h"
 #include "chromeos/ash/components/osauth/public/auth_session_storage.h"
 
 namespace ash {
 namespace multidevice_setup {
 
-AuthTokenValidatorImpl::AuthTokenValidatorImpl(
-    quick_unlock::QuickUnlockStorage* quick_unlock_storage)
-    : quick_unlock_storage_(quick_unlock_storage) {}
+AuthTokenValidatorImpl::AuthTokenValidatorImpl() = default;
 
 AuthTokenValidatorImpl::~AuthTokenValidatorImpl() = default;
 
 bool AuthTokenValidatorImpl::IsAuthTokenValid(const std::string& auth_token) {
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    return ash::AuthSessionStorage::Get()->IsValid(auth_token);
-  } else {
-    return quick_unlock_storage_ && quick_unlock_storage_->GetAuthToken() &&
-           auth_token == quick_unlock_storage_->GetAuthToken()->Identifier();
-  }
-}
-
-void AuthTokenValidatorImpl::Shutdown() {
-  quick_unlock_storage_ = nullptr;
+  return ash::AuthSessionStorage::Get()->IsValid(auth_token);
 }
 
 }  // namespace multidevice_setup
diff --git a/chrome/browser/ash/multidevice_setup/auth_token_validator_impl.h b/chrome/browser/ash/multidevice_setup/auth_token_validator_impl.h
index 2918ffde..87deef6 100644
--- a/chrome/browser/ash/multidevice_setup/auth_token_validator_impl.h
+++ b/chrome/browser/ash/multidevice_setup/auth_token_validator_impl.h
@@ -11,10 +11,6 @@
 
 namespace ash {
 
-namespace quick_unlock {
-class QuickUnlockStorage;
-}
-
 namespace multidevice_setup {
 
 // Concrete AuthTokenValidator implementation.
@@ -24,8 +20,7 @@
 // should be added.
 class AuthTokenValidatorImpl : public AuthTokenValidator, public KeyedService {
  public:
-  AuthTokenValidatorImpl(
-      quick_unlock::QuickUnlockStorage* quick_unlock_storage);
+  AuthTokenValidatorImpl();
 
   AuthTokenValidatorImpl(const AuthTokenValidatorImpl&) = delete;
   AuthTokenValidatorImpl& operator=(const AuthTokenValidatorImpl&) = delete;
@@ -33,13 +28,6 @@
   ~AuthTokenValidatorImpl() override;
 
   bool IsAuthTokenValid(const std::string& auth_token) override;
-
- private:
-  // KeyedService:
-  void Shutdown() override;
-
-  raw_ptr<quick_unlock::QuickUnlockStorage, ExperimentalAsh>
-      quick_unlock_storage_;
 };
 
 }  // namespace multidevice_setup
diff --git a/chrome/browser/ash/ownership/owner_key_loader.cc b/chrome/browser/ash/ownership/owner_key_loader.cc
index cc23339..5b95033 100644
--- a/chrome/browser/ash/ownership/owner_key_loader.cc
+++ b/chrome/browser/ash/ownership/owner_key_loader.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/ash/ownership/ownership_histograms.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/ash/settings/device_settings_service.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/net/nss_service.h"
 #include "chrome/browser/net/nss_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -30,6 +31,10 @@
 // Max number of attempts to generate a new owner key.
 constexpr int kMaxGenerateAttempts = 5;
 
+using WorkerTask =
+    base::OnceCallback<void(crypto::ScopedPK11Slot /*public_slot*/,
+                            crypto::ScopedPK11Slot /*private_slot*/)>;
+
 void LoadPublicKeyOnlyOnWorkerThread(
     scoped_refptr<ownership::OwnerKeyUtil> owner_key_util,
     base::OnceCallback<void(scoped_refptr<ownership::PublicKey>)>
@@ -45,21 +50,22 @@
     scoped_refptr<ownership::PublicKey> public_key,
     base::OnceCallback<void(scoped_refptr<ownership::PrivateKey>,
                             bool found_in_public_slot)> ui_thread_callback,
-    net::NSSCertDatabase* database) {
+    crypto::ScopedPK11Slot public_slot,
+    crypto::ScopedPK11Slot private_slot) {
   // TODO(davidben): FindPrivateKeyInSlot internally checks for a null slot if
   // needbe. The null check should be in the caller rather than internally in
   // the OwnerKeyUtil implementation. The tests currently get a null
   // private_slot and expect the mock OwnerKeyUtil to still be called.
   scoped_refptr<ownership::PrivateKey> private_key =
       base::MakeRefCounted<ownership::PrivateKey>(
-          owner_key_util->FindPrivateKeyInSlot(
-              public_key->data(), database->GetPrivateSlot().get()));
+          owner_key_util->FindPrivateKeyInSlot(public_key->data(),
+                                               private_slot.get()));
   bool found_in_public_slot = false;
 
   if (!private_key->key()) {
     private_key = base::MakeRefCounted<ownership::PrivateKey>(
         owner_key_util->FindPrivateKeyInSlot(public_key->data(),
-                                             database->GetPublicSlot().get()));
+                                             public_slot.get()));
     if (private_key->key()) {
       // If the key is stored in the public slot, it might need to be migrated
       // (depending on the experiment).
@@ -76,16 +82,15 @@
     base::OnceCallback<void(scoped_refptr<ownership::PublicKey>,
                             scoped_refptr<ownership::PrivateKey>)>
         ui_thread_callback,
-    net::NSSCertDatabase* nss_db) {
+    crypto::ScopedPK11Slot public_slot,
+    crypto::ScopedPK11Slot private_slot) {
   crypto::ScopedSECKEYPrivateKey sec_priv_key;
-  if (features::IsStoreOwnerKeyInPrivateSlotEnabled()) {
-    sec_priv_key =
-        owner_key_util->GenerateKeyPair(nss_db->GetPrivateSlot().get());
+  if (private_slot && features::IsStoreOwnerKeyInPrivateSlotEnabled()) {
+    sec_priv_key = owner_key_util->GenerateKeyPair(private_slot.get());
     RecordOwnerKeyEvent(OwnerKeyEvent::kPrivateSlotKeyGeneration,
                         bool(sec_priv_key));
-  } else {
-    sec_priv_key =
-        owner_key_util->GenerateKeyPair(nss_db->GetPublicSlot().get());
+  } else if (public_slot) {
+    sec_priv_key = owner_key_util->GenerateKeyPair(public_slot.get());
     RecordOwnerKeyEvent(OwnerKeyEvent::kPublicSlotKeyGeneration,
                         bool(sec_priv_key));
   }
@@ -123,10 +128,10 @@
                                 std::move(public_key), std::move(private_key)));
 }
 
-void PostOnWorkerThreadWithCertDb(
-    base::OnceCallback<void(net::NSSCertDatabase*)> worker_task,
-    net::NSSCertDatabase* nss_db) {
+void PostOnWorkerThreadWithCertDb(WorkerTask worker_task,
+                                  net::NSSCertDatabase* nss_db) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  CHECK(nss_db);
 
   // TODO(eseckler): It seems loading the key is important for the UsersPrivate
   // extension API to work correctly during startup, which is why we cannot
@@ -135,12 +140,12 @@
       FROM_HERE,
       {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
        base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
-      base::BindOnce(std::move(worker_task), nss_db));
+      base::BindOnce(std::move(worker_task), nss_db->GetPublicSlot(),
+                     nss_db->GetPrivateSlot()));
 }
 
-void GetCertDbAndPostOnWorkerThreadOnIO(
-    NssCertDatabaseGetter nss_getter,
-    base::OnceCallback<void(net::NSSCertDatabase*)> worker_task) {
+void GetCertDbAndPostOnWorkerThreadOnIO(NssCertDatabaseGetter nss_getter,
+                                        WorkerTask worker_task) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   // Running |nss_getter| may either return a non-null pointer
@@ -156,9 +161,7 @@
   }
 }
 
-void GetCertDbAndPostOnWorkerThread(
-    Profile* profile,
-    base::OnceCallback<void(net::NSSCertDatabase*)> worker_task) {
+void GetCertDbAndPostOnWorkerThread(Profile* profile, WorkerTask worker_task) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   content::GetIOThreadTaskRunner({})->PostTask(
       FROM_HERE, base::BindOnce(&GetCertDbAndPostOnWorkerThreadOnIO,
@@ -228,6 +231,12 @@
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(callback_) << "Run() can only be called once.";
 
+  if (g_browser_process && g_browser_process->IsShuttingDown()) {
+    return std::move(callback_).Run(/*public_key=*/nullptr,
+                                    /*private_key=*/nullptr);
+    // `this` might be deleted here.
+  }
+
   if (!device_settings_service_) {
     CHECK_IS_TEST();
     RecordOwnerKeyEvent(OwnerKeyEvent::kDeviceSettingsServiceIsNull,
diff --git a/chrome/browser/ash/ownership/owner_key_loader_unittest.cc b/chrome/browser/ash/ownership/owner_key_loader_unittest.cc
index e54c0aad..0dd1e98 100644
--- a/chrome/browser/ash/ownership/owner_key_loader_unittest.cc
+++ b/chrome/browser/ash/ownership/owner_key_loader_unittest.cc
@@ -649,6 +649,21 @@
                   Bucket(OwnerKeyUmaEvent::kMigrationToPublicSlotStarted, 1)));
 }
 
+// Test that OwnerKeyLoader silently exits if it was started after the shutdown
+// had started.
+TEST_F(RegularOwnerKeyLoaderTest, ExitOnShutdown) {
+  // In real code DeviceSettingsService must call this for the first user.
+  device_settings_service_.MarkWillEstablishConsumerOwnership();
+  // Do not prepare any keys, so key_loader_ has to generate a new one.
+  TestingBrowserProcess::GetGlobal()->SetShuttingDown(true);
+
+  key_loader_->Run();
+
+  EXPECT_FALSE(result_observer_.Get<PublicKeyRefPtr>());
+  EXPECT_FALSE(result_observer_.Get<PrivateKeyRefPtr>());
+  EXPECT_EQ(histogram_tester_.GetTotalSum(kOwnerKeyHistogramName), 0);
+}
+
 class ChildOwnerKeyLoaderTest : public OwnerKeyLoaderTestBase {
  public:
   ChildOwnerKeyLoaderTest()
diff --git a/chrome/browser/autofill/credit_card_accessory_controller.h b/chrome/browser/autofill/credit_card_accessory_controller.h
index cc5afc1a..dcfd7f2 100644
--- a/chrome/browser/autofill/credit_card_accessory_controller.h
+++ b/chrome/browser/autofill/credit_card_accessory_controller.h
@@ -19,8 +19,7 @@
 // Interface for credit card-specific keyboard accessory controller between the
 // ManualFillingController and Autofill backend logic.
 class CreditCardAccessoryController : public AccessoryController,
-                                      public PersonalDataManagerObserver,
-                                      public CreditCardAccessManager::Accessor {
+                                      public PersonalDataManagerObserver {
  public:
   CreditCardAccessoryController() = default;
   ~CreditCardAccessoryController() override = default;
diff --git a/chrome/browser/autofill/credit_card_accessory_controller_impl.cc b/chrome/browser/autofill/credit_card_accessory_controller_impl.cc
index af8ec31..0115e78 100644
--- a/chrome/browser/autofill/credit_card_accessory_controller_impl.cc
+++ b/chrome/browser/autofill/credit_card_accessory_controller_impl.cc
@@ -11,6 +11,7 @@
 #include "base/check.h"
 #include "base/containers/cxx20_erase_vector.h"
 #include "base/debug/dump_without_crashing.h"
+#include "base/functional/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/utf_string_conversions.h"
@@ -244,7 +245,9 @@
 
   last_focused_field_id_ = focused_field_id;
   GetManager()->GetCreditCardAccessManager()->FetchCreditCard(
-      UnwrapCardOrVirtualCard(*card_iter), AsWeakPtr());
+      UnwrapCardOrVirtualCard(*card_iter),
+      base::BindOnce(&CreditCardAccessoryControllerImpl::OnCreditCardFetched,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 void CreditCardAccessoryControllerImpl::OnPasskeySelected(
diff --git a/chrome/browser/autofill/credit_card_accessory_controller_impl.h b/chrome/browser/autofill/credit_card_accessory_controller_impl.h
index ce33b7e..bc60fe1e 100644
--- a/chrome/browser/autofill/credit_card_accessory_controller_impl.h
+++ b/chrome/browser/autofill/credit_card_accessory_controller_impl.h
@@ -43,10 +43,6 @@
   // PersonalDataManagerObserver:
   void OnPersonalDataChanged() override;
 
-  // CreditCardAccessManager::Accessor:
-  void OnCreditCardFetched(CreditCardFetchResult result,
-                           const CreditCard* credit_card) override;
-
   static void CreateForWebContentsForTesting(
       content::WebContents* web_contents,
       base::WeakPtr<ManualFillingController> mf_controller,
@@ -83,6 +79,11 @@
   // plaintext and won't require any authentication when filling is triggered.
   std::vector<const CachedServerCardInfo*> GetUnmaskedCreditCards() const;
 
+  // `OnFillingTriggered()` fetches the credit card and calls this function
+  // once the `credit_card` is available. If successful, it fills it.
+  void OnCreditCardFetched(CreditCardFetchResult result,
+                           const CreditCard* credit_card);
+
   // Gets promo code offers from personal data manager.
   std::vector<const AutofillOfferData*> GetPromoCodeOffers() const;
 
diff --git a/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc b/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc
index 225c073..63a7174d 100644
--- a/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc
+++ b/chrome/browser/autofill/credit_card_accessory_controller_impl_unittest.cc
@@ -68,9 +68,11 @@
                                 personal_data,
                                 /*credit_card_form_event_logger=*/nullptr) {}
 
-  void FetchCreditCard(const CreditCard* card,
-                       base::WeakPtr<Accessor> accessor) override {
-    accessor->OnCreditCardFetched(CreditCardFetchResult::kSuccess, card);
+  void FetchCreditCard(
+      const CreditCard* card,
+      OnCreditCardFetchedCallback on_credit_card_fetched) override {
+    std::move(on_credit_card_fetched)
+        .Run(CreditCardFetchResult::kSuccess, card);
   }
 };
 
diff --git a/chrome/browser/autofill/mock_credit_card_accessory_controller.h b/chrome/browser/autofill/mock_credit_card_accessory_controller.h
index b27ace7..f7938a42 100644
--- a/chrome/browser/autofill/mock_credit_card_accessory_controller.h
+++ b/chrome/browser/autofill/mock_credit_card_accessory_controller.h
@@ -43,10 +43,6 @@
               (override));
   MOCK_METHOD(void, RefreshSuggestions, (), (override));
   MOCK_METHOD(void, OnPersonalDataChanged, (), (override));
-  MOCK_METHOD(void,
-              OnCreditCardFetched,
-              (autofill::CreditCardFetchResult, const autofill::CreditCard*),
-              (override));
 
   base::WeakPtr<CreditCardAccessoryController> AsWeakPtr() override {
     return weak_ptr_factory_.GetWeakPtr();
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_model_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_model_delegate.cc
index 01ef782..3465a5c 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_model_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_model_delegate.cc
@@ -14,6 +14,8 @@
 #include "components/browsing_topics/browsing_topics_service.h"
 #include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/media_device_salt/media_device_salt_service.h"
+#include "components/supervised_user/core/common/buildflags.h"
+#include "components/supervised_user/core/common/features.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/storage_partition_config.h"
 #include "url/origin.h"
@@ -25,6 +27,15 @@
 #include "chrome/browser/web_applications/web_app_provider.h"
 #endif
 
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
+#include "components/supervised_user/core/browser/supervised_user_service.h"
+#endif
+
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
+#include "components/permissions/permissions_client.h"
+#endif
+
 namespace {
 
 class DynamicBarrierClosure : public base::RefCounted<DynamicBarrierClosure> {
@@ -200,6 +211,27 @@
   }
 }
 
+bool ChromeBrowsingDataModelDelegate::IsCookieDeletionDisabled(
+    const GURL& url) {
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+  supervised_user::SupervisedUserService* supervised_user_service =
+      SupervisedUserServiceFactory::GetForBrowserContext(profile_);
+  if (!supervised_user_service) {
+    // For some Profiles (e.g. Incognito), SupervisedUserService is not
+    // created.
+    return false;
+  }
+  return supervised_user_service->IsCookieDeletionDisabled(url);
+#elif BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
+  if (profile_->IsChild()) {
+    auto* client = permissions::PermissionsClient::Get();
+    return client->IsCookieDeletionDisabled(profile_, url);
+  }
+#else
+  return false;
+#endif
+}
+
 void ChromeBrowsingDataModelDelegate::GetAllMediaDeviceSaltDataKeys(
     base::OnceCallback<void(std::vector<DelegateEntry>)> callback,
     std::vector<DelegateEntry> entries) {
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_model_delegate.h b/chrome/browser/browsing_data/chrome_browsing_data_model_delegate.h
index 3255d521..0be4654 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_model_delegate.h
+++ b/chrome/browser/browsing_data/chrome_browsing_data_model_delegate.h
@@ -57,6 +57,7 @@
       BrowsingDataModel::StorageType storage_type) const override;
   absl::optional<bool> IsBlockedByThirdPartyCookieBlocking(
       BrowsingDataModel::StorageType storage_type) const override;
+  bool IsCookieDeletionDisabled(const GURL& url) override;
 
  private:
   ChromeBrowsingDataModelDelegate(Profile* profile,
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_model_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_model_delegate_unittest.cc
index e7a2be9a..e8d6e8b5 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_model_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_model_delegate_unittest.cc
@@ -24,6 +24,10 @@
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+#include "components/supervised_user/core/common/features.h"
+#endif
+
 #if !BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
@@ -67,8 +71,15 @@
 class ChromeBrowsingDataModelDelegateTest : public testing::Test {
  public:
   ChromeBrowsingDataModelDelegateTest() {
-    feature_list_.InitAndEnableFeature(
-        media_device_salt::kMediaDeviceIdPartitioning);
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/
+        {
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+          supervised_user::kClearingCookiesKeepsSupervisedUsersSignedIn,
+#endif
+              media_device_salt::kMediaDeviceIdPartitioning
+        },
+        /*disabled_features=*/{});
   }
 
   ChromeBrowsingDataModelDelegateTest(
@@ -245,3 +256,46 @@
             remover->GetLastUsedRemovalMaskForTesting());
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
+
+// TODO(crbug.com/1493504): Re-enable on macOS once flakiness is resolved.
+#if !BUILDFLAG(IS_MAC) && BUILDFLAG(ENABLE_SUPERVISED_USERS)
+TEST_F(ChromeBrowsingDataModelDelegateTest, CookieDeletionFilterChildUser) {
+  profile_->SetIsSupervisedProfile(true);
+
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("https://google.com")));
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("https://example.com")));
+  EXPECT_TRUE(delegate()->IsCookieDeletionDisabled(GURL("http://youtube.com")));
+  EXPECT_TRUE(
+      delegate()->IsCookieDeletionDisabled(GURL("https://youtube.com")));
+}
+
+TEST_F(ChromeBrowsingDataModelDelegateTest, CookieDeletionFilterNormalUser) {
+  profile_->SetIsSupervisedProfile(false);
+
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("https://google.com")));
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("https://example.com")));
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("http://youtube.com")));
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("https://youtube.com")));
+}
+
+TEST_F(ChromeBrowsingDataModelDelegateTest, CookieDeletionFilterIncognitoUser) {
+  // Replace the delegate with an incognito profile delegate.
+  delegate_ = ChromeBrowsingDataModelDelegate::CreateForProfile(
+      profile_->GetOffTheRecordProfile(Profile::OTRProfileID::PrimaryID(),
+                                       /*create_if_needed=*/true));
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("https://google.com")));
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("https://example.com")));
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("http://youtube.com")));
+  EXPECT_FALSE(
+      delegate()->IsCookieDeletionDisabled(GURL("https://youtube.com")));
+}
+#endif  // !BUILDFLAG(IS_MAC) && BUILDFLAG(ENABLE_SUPERVISED_USERS)
diff --git a/chrome/browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer.cc b/chrome/browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer.cc
index 74397774..5b727f1 100644
--- a/chrome/browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer.cc
+++ b/chrome/browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer.cc
@@ -108,7 +108,15 @@
           IsMicrosoftOneDriveIntegrationForEnterpriseEnabled()) {
     return nullptr;
   }
-  return OneDrivePrefObserver::Create(Profile::FromBrowserContext(context));
+
+  Profile* profile = Profile::FromBrowserContext(context);
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  if (!profile->IsMainProfile()) {
+    return nullptr;
+  }
+#endif
+
+  return OneDrivePrefObserver::Create(profile);
 }
 
 bool OneDrivePrefObserverFactory::ServiceIsCreatedWithBrowserContext() const {
diff --git a/chrome/browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer_browsertest.cc b/chrome/browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer_browsertest.cc
new file mode 100644
index 0000000..d4a4cc6
--- /dev/null
+++ b/chrome/browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer_browsertest.cc
@@ -0,0 +1,59 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/contains.h"
+#include "chrome/browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer.h"
+
+#include "base/containers/contains.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/core/dependency_graph.h"
+#include "components/keyed_service/core/keyed_service_base_factory.h"
+#include "content/public/test/browser_test.h"
+
+namespace chromeos::cloud_storage {
+
+class OneDrivePrefObserverBrowserTest : public InProcessBrowserTest {
+ public:
+  OneDrivePrefObserverBrowserTest() {
+    feature_list_.InitWithFeatures(
+        {chromeos::features::kUploadOfficeToCloud,
+         chromeos::features::kMicrosoftOneDriveIntegrationForEnterprise},
+        {});
+  }
+  ~OneDrivePrefObserverBrowserTest() override = default;
+
+ protected:
+  bool OneDrivePrefObserverServiceExists() {
+    std::vector<DependencyNode*> nodes;
+    const bool success = BrowserContextDependencyManager::GetInstance()
+                             ->GetDependencyGraphForTesting()
+                             .GetConstructionOrder(&nodes);
+    EXPECT_TRUE(success);
+    return base::Contains(
+        nodes, "OneDrivePrefObserverFactory",
+        [](const DependencyNode* node) -> std::string_view {
+          return static_cast<const KeyedServiceBaseFactory*>(node)->name();
+        });
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(OneDrivePrefObserverBrowserTest,
+                       KeyedServiceRegistered) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  ASSERT_TRUE(OneDrivePrefObserverServiceExists());
+#elif BUILDFLAG(IS_CHROMEOS_ASH)
+  ASSERT_NE(crosapi::browser_util::IsLacrosEnabled(),
+            OneDrivePrefObserverServiceExists());
+#else
+  NOTREACHED();
+#endif
+}
+
+}  // namespace chromeos::cloud_storage
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
index 659636d2d..1ff1e79e 100644
--- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
+++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -616,7 +616,7 @@
 
   // Add a new IBAN and return if this is not an update.
   if (!existing_iban) {
-    personal_data->AddIban(iban_to_write);
+    personal_data->AddAsLocalIban(iban_to_write);
     base::RecordAction(base::UserMetricsAction("AutofillIbanAdded"));
     if (!iban_to_write.nickname().empty()) {
       base::RecordAction(
diff --git a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.cc b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.cc
index 992be90..9e049f0 100644
--- a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.cc
+++ b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.cc
@@ -67,8 +67,7 @@
 const char kInternalError[] = "Internal error.";
 const char kWeakCredential[] = "Weak credential.";
 
-const char kAuthTokenExpired[] = "Authentication token expired.";
-const char kAuthTokenInvalid[] = "Authentication token invalid.";
+const char kAuthTokenExpired[] = "Authentication token invalid or expired.";
 
 // PINs greater in length than |kMinLengthForWeakPin| will be checked for
 // weakness.
@@ -203,28 +202,11 @@
   return profile;
 }
 
-AuthToken* GetActiveProfileAuthToken(content::BrowserContext* browser_context) {
-  CHECK(!ash::features::ShouldUseAuthSessionStorage());
-  return ash::quick_unlock::QuickUnlockFactory::GetForProfile(
-             GetActiveProfile(browser_context))
-      ->GetAuthToken();
-}
-
 absl::optional<std::string> CheckTokenValidity(
     content::BrowserContext* browser_context,
     const std::string& token) {
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (!ash::AuthSessionStorage::Get()->IsValid(token)) {
-      return kAuthTokenExpired;
-    }
-  } else {
-    AuthToken* auth_token = GetActiveProfileAuthToken(browser_context);
-    if (!auth_token) {
-      return kAuthTokenExpired;
-    }
-    if (token != auth_token->Identifier()) {
-      return kAuthTokenInvalid;
-    }
+  if (!ash::AuthSessionStorage::Get()->IsValid(token)) {
+    return kAuthTokenExpired;
   }
   return absl::nullopt;
 }
@@ -261,8 +243,7 @@
     absl::optional<ash::AuthenticationError> error) {
   if (!token_info.has_value()) {
     DCHECK(error.has_value());
-    Respond(
-        Error(LegacyQuickUnlockPrivateGetAuthTokenHelper::kPasswordIncorrect));
+    Respond(Error(kInvalidCredential));
     return;
   }
 
diff --git a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
index 810c523..94c62dd 100644
--- a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api_unittest.cc
@@ -28,8 +28,6 @@
 #include "chrome/browser/ash/login/quick_unlock/auth_token.h"
 #include "chrome/browser/ash/login/quick_unlock/pin_backend.h"
 #include "chrome/browser/ash/login/quick_unlock/pin_storage_prefs.h"
-#include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
-#include "chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h"
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
 #include "chrome/browser/ash/login/smart_lock/smart_lock_service.h"
 #include "chrome/browser/ash/login/smart_lock/smart_lock_service_factory.h"
@@ -41,6 +39,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/ash/components/cryptohome/constants.h"
 #include "chromeos/ash/components/cryptohome/system_salt_getter.h"
 #include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
 #include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
@@ -272,13 +271,8 @@
           ash::AuthFactorsConfiguration());
     }
 
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      token_ = ash::AuthSessionStorage::Get()->Store(
-          std::make_unique<ash::UserContext>(auth_token_user_context_));
-    } else {
-      token_ = ash::quick_unlock::QuickUnlockFactory::GetForProfile(profile)
-                   ->CreateAuthToken(auth_token_user_context_);
-    }
+    token_ = ash::AuthSessionStorage::Get()->Store(
+        std::make_unique<ash::UserContext>(auth_token_user_context_));
 
     base::RunLoop().RunUntilIdle();
 
@@ -702,16 +696,9 @@
   absl::optional<quick_unlock_private::TokenInfo> token_info =
       GetAuthToken(kValidPassword);
 
-  ash::quick_unlock::QuickUnlockStorage* quick_unlock_storage =
-      ash::quick_unlock::QuickUnlockFactory::GetForProfile(profile());
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    EXPECT_TRUE(ash::AuthSessionStorage::Get()->IsValid(token_info->token));
-  } else {
-    EXPECT_EQ(token_info->token,
-              quick_unlock_storage->GetAuthToken()->Identifier());
-  }
+  EXPECT_TRUE(ash::AuthSessionStorage::Get()->IsValid(token_info->token));
   EXPECT_EQ(token_info->lifetime_seconds,
-            ash::quick_unlock::AuthToken::kTokenExpiration.InSeconds());
+            cryptohome::kAuthsessionInitialLifetime.InSeconds());
 }
 
 // Verifies that GetAuthTokenValid fails when an invalid password is provided.
diff --git a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.cc b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.cc
index f9c9848..3cd205f 100644
--- a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.cc
+++ b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.cc
@@ -17,8 +17,8 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/quick_unlock_private.h"
+#include "chromeos/ash/components/cryptohome/constants.h"
 #include "chromeos/ash/components/login/auth/auth_performer.h"
-#include "chromeos/ash/components/login/auth/extended_authenticator.h"
 #include "chromeos/ash/components/login/auth/public/user_context.h"
 #include "chromeos/ash/components/osauth/public/auth_session_storage.h"
 #include "components/user_manager/known_user.h"
@@ -31,73 +31,6 @@
 using TokenInfo = api::quick_unlock_private::TokenInfo;
 using QuickUnlockStorage = ash::quick_unlock::QuickUnlockStorage;
 
-/******** LegacyQuickUnlockPrivateGetAuthTokenHelper ********/
-
-const char LegacyQuickUnlockPrivateGetAuthTokenHelper::kPasswordIncorrect[] =
-    "Incorrect Password.";
-
-LegacyQuickUnlockPrivateGetAuthTokenHelper::
-    LegacyQuickUnlockPrivateGetAuthTokenHelper(Profile* profile)
-    : profile_(profile) {}
-
-LegacyQuickUnlockPrivateGetAuthTokenHelper::
-    ~LegacyQuickUnlockPrivateGetAuthTokenHelper() = default;
-
-void LegacyQuickUnlockPrivateGetAuthTokenHelper::Run(
-    ash::ExtendedAuthenticator* extended_authenticator,
-    const std::string& password,
-    ResultCallback callback) {
-  callback_ = std::move(callback);
-
-  const user_manager::User* const user =
-      ash::ProfileHelper::Get()->GetUserByProfile(profile_);
-  ash::UserContext user_context(*user);
-  user_context.SetKey(ash::Key(password));
-
-  // Balanced in `OnAuthFailure` and `OnAuthSuccess`.
-  AddRef();
-
-  content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(&ash::ExtendedAuthenticator::AuthenticateToCheck,
-                     extended_authenticator, user_context,
-                     base::OnceClosure()));
-}
-
-void LegacyQuickUnlockPrivateGetAuthTokenHelper::OnAuthFailure(
-    const ash::AuthFailure& error) {
-  std::move(callback_).Run(false, nullptr, kPasswordIncorrect);
-
-  Release();  // Balanced in Run().
-}
-
-void LegacyQuickUnlockPrivateGetAuthTokenHelper::OnAuthSuccess(
-    const ash::UserContext& user_context) {
-  auto token_info = std::make_unique<TokenInfo>();
-
-  QuickUnlockStorage* quick_unlock_storage =
-      ash::quick_unlock::QuickUnlockFactory::GetForProfile(profile_);
-  quick_unlock_storage->MarkStrongAuth();
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    token_info->token = ash::AuthSessionStorage::Get()->Store(
-        std::make_unique<ash::UserContext>(user_context));
-    // TODO(b/238606050): Determine authsession lifetime.
-    token_info->lifetime_seconds = AuthToken::kTokenExpiration.InSeconds();
-  } else {
-    token_info->token = quick_unlock_storage->CreateAuthToken(user_context);
-    token_info->lifetime_seconds = AuthToken::kTokenExpiration.InSeconds();
-  }
-
-  // The user has successfully authenticated, so we should reset pin/fingerprint
-  // attempt counts.
-  quick_unlock_storage->pin_storage_prefs()->ResetUnlockAttemptCount();
-  quick_unlock_storage->fingerprint_storage()->ResetUnlockAttemptCount();
-
-  std::move(callback_).Run(true, std::move(token_info), "");
-
-  Release();  // Balanced in Run().
-}
-
 QuickUnlockPrivateGetAuthTokenHelper::QuickUnlockPrivateGetAuthTokenHelper(
     Profile* profile,
     std::string password)
@@ -217,16 +150,10 @@
   quick_unlock_storage->fingerprint_storage()->ResetUnlockAttemptCount();
 
   TokenInfo token_info;
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    token_info.token =
-        ash::AuthSessionStorage::Get()->Store(std::move(user_context));
-    // TODO(b/238606050): Determine authsession lifetime.
-    token_info.lifetime_seconds = AuthToken::kTokenExpiration.InSeconds();
-  } else {
-    token_info.token =
-        quick_unlock_storage->CreateAuthToken(std::move(*user_context));
-    token_info.lifetime_seconds = AuthToken::kTokenExpiration.InSeconds();
-  }
+  token_info.token =
+      ash::AuthSessionStorage::Get()->Store(std::move(user_context));
+  token_info.lifetime_seconds =
+      cryptohome::kAuthsessionInitialLifetime.InSeconds();
 
   std::move(callback).Run(std::move(token_info), absl::nullopt);
 }
diff --git a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.h b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.h
index 178dce6a..fa21b3aac 100644
--- a/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.h
+++ b/chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_ash_utils.h
@@ -25,7 +25,6 @@
 class Profile;
 
 namespace ash {
-class ExtendedAuthenticator;
 class UserContext;
 class AuthenticationError;
 class AuthPerformer;
@@ -39,69 +38,6 @@
 }  // namespace quick_unlock_private
 }  // namespace api
 
-//
-// A single-use adaptor to make calls to
-//   ash::ExtendedAuthenticator::AuthenticateToCheck()
-// and pass result back to a single callback. Re. object lifetime, caller just
-// have to call:
-//
-//   scoped_refptr<LegacyQuickUnlockPrivateGetAuthTokenHelper> helper =
-//      base::MakeRefCounted<LegacyQuickUnlockPrivateGetAuthTokenHelper>(...);
-//   ...
-//   // Attach |helper| to a ash::ExtendedAuthenticator.
-//   ...
-//   // Bind callback and pass as argument.
-//   helper->Run(...);
-//
-// Hereafter, the caller need not worry about |helper|'s lifetime.
-class LegacyQuickUnlockPrivateGetAuthTokenHelper
-    : public ash::AuthStatusConsumer,
-      public base::RefCountedThreadSafe<
-          LegacyQuickUnlockPrivateGetAuthTokenHelper,
-          content::BrowserThread::DeleteOnUIThread> {
- public:
-  using TokenInfo = api::quick_unlock_private::TokenInfo;
-
-  // The only error message that this class ever returns, even if the error is
-  // some internal error and not due to an incorrect password. Should
-  // eventually be refactored/removed together with this legacy class.
-  static const char kPasswordIncorrect[];
-
-  // |error_message| is empty if |success|, and non-empty otherwise.
-  // |token_info| is non-null if |success|, and null otherwise.
-  using ResultCallback =
-      base::OnceCallback<void(bool success,
-                              std::unique_ptr<TokenInfo> token_info,
-                              const std::string& error_message)>;
-
-  explicit LegacyQuickUnlockPrivateGetAuthTokenHelper(Profile* profile);
-  LegacyQuickUnlockPrivateGetAuthTokenHelper(
-      const LegacyQuickUnlockPrivateGetAuthTokenHelper&) = delete;
-  LegacyQuickUnlockPrivateGetAuthTokenHelper& operator=(
-      const LegacyQuickUnlockPrivateGetAuthTokenHelper&) = delete;
-
-  void Run(ash::ExtendedAuthenticator* extended_authenticator,
-           const std::string& password,
-           ResultCallback callback);
-
- protected:
-  ~LegacyQuickUnlockPrivateGetAuthTokenHelper() override;
-
- private:
-  friend class base::RefCountedThreadSafe<
-      LegacyQuickUnlockPrivateGetAuthTokenHelper>;
-  friend class base::DeleteHelper<LegacyQuickUnlockPrivateGetAuthTokenHelper>;
-  friend struct content::BrowserThread::DeleteOnThread<
-      content::BrowserThread::UI>;
-
-  // AuthStatusConsumer overrides.
-  void OnAuthFailure(const ash::AuthFailure& error) override;
-  void OnAuthSuccess(const ash::UserContext& user_context) override;
-
-  raw_ptr<Profile> profile_;
-  ResultCallback callback_;
-};
-
 class QuickUnlockPrivateGetAuthTokenHelper {
  public:
   QuickUnlockPrivateGetAuthTokenHelper(Profile*, std::string password);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 83b96a3..34b1175 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1953,6 +1953,15 @@
     "expiry_milestone": 99
   },
   {
+    "name": "element-capture",
+    "owners": [
+      "eladalon@chromium.org",
+      "jophba@chromium.org",
+      "mfoltz@chromium.org"
+    ],
+    "expiry_milestone": 140
+  },
+  {
     "name": "enable-accessibility-accelerators-notifications-timeout",
     "owners": [ "katie@chromium.org", "//ash/accessibility/OWNERS" ],
     "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 35d8917..4c8bcdf 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -7743,6 +7743,13 @@
     "Enables Elastic Overscrolling on touchscreens and precision touchpads.";
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
 
+#if !BUILDFLAG(IS_ANDROID)
+const char kElementCaptureName[] = "Element Capture";
+const char kElementCaptureDescription[] =
+    "Enables Element Capture - an API allowing the mutation of a tab-capture "
+    "media track into a track capturing just a specific DOM element.";
+#endif  // !BUILDFLAG(IS_ANDROID)
+
 #if BUILDFLAG(IS_WIN) ||                                      \
     (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) || \
     BUILDFLAG(IS_MAC) || BUILDFLAG(IS_FUCHSIA)
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index e25a50f8..a8db3033 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -4470,6 +4470,11 @@
 extern const char kElasticOverscrollDescription[];
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
 
+#if !BUILDFLAG(IS_ANDROID)
+extern const char kElementCaptureName[];
+extern const char kElementCaptureDescription[];
+#endif  // !BUILDFLAG(IS_ANDROID)
+
 #if BUILDFLAG(IS_WIN) ||                                      \
     (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) || \
     BUILDFLAG(IS_MAC) || BUILDFLAG(IS_FUCHSIA)
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index d43a0317c..531b5cf 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -47,6 +47,7 @@
 #include "components/paint_preview/features/features.h"
 #include "components/password_manager/core/browser/features/password_features.h"
 #include "components/password_manager/core/common/password_manager_features.h"
+#include "components/permissions/features.h"
 #include "components/policy/core/common/features.h"
 #include "components/privacy_sandbox/privacy_sandbox_features.h"
 #include "components/query_tiles/switches.h"
@@ -112,6 +113,7 @@
     &features::kGenericSensorExtraClasses,
     &features::kBackForwardCache,
     &features::kBackForwardTransitions,
+    &features::kBlockMidiByDefault,
     &features::kMetricsSettingsAndroid,
     &features::kNetworkServiceInProcess,
     &shared_highlighting::kPreemptiveLinkToTextGeneration,
@@ -376,7 +378,7 @@
     &password_manager::features::kRecoverFromNeverSaveAndroid,
     &password_manager::features::
         kUnifiedPasswordManagerLocalPasswordsMigrationWarning,
-    &features::kBlockMidiByDefault,
+    &permissions::features::kPermissionsPromptSurvey,
     &privacy_sandbox::kPrivacySandboxFirstPartySetsUI,
     &privacy_sandbox::kPrivacySandboxSettings3,
     &privacy_sandbox::kPrivacySandboxSettings4,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 2f06dbd..5b80db0 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -359,6 +359,7 @@
             "PasskeyManagementUsingAccountSettingsAndroid";
     public static final String PASSWORD_GENERATION_BOTTOM_SHEET = "PasswordGenerationBottomSheet";
     public static final String PASSWORD_EDIT_DIALOG_WITH_DETAILS = "PasswordEditDialogWithDetails";
+    public static final String PERMISSION_PROMPT_SURVEY = "PermissionPromptSurvey";
     public static final String PORTALS = "Portals";
     public static final String PORTALS_CROSS_ORIGIN = "PortalsCrossOrigin";
     public static final String PREEMPTIVE_LINK_TO_TEXT_GENERATION =
diff --git a/chrome/browser/incognito/OWNERS b/chrome/browser/incognito/OWNERS
index 7e736b92..12de98e 100644
--- a/chrome/browser/incognito/OWNERS
+++ b/chrome/browser/incognito/OWNERS
@@ -1,2 +1,3 @@
 dullweber@chromium.org
+sideyilmaz@chromium.org
 zalmashni@google.com
diff --git a/chrome/browser/password_check/android/password_check_bridge.cc b/chrome/browser/password_check/android/password_check_bridge.cc
index 31d432d..40bb3078 100644
--- a/chrome/browser/password_check/android/password_check_bridge.cc
+++ b/chrome/browser/password_check/android/password_check_bridge.cc
@@ -130,7 +130,7 @@
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& activity) {
   PasswordCheckupLauncherHelperImpl checkup_launcher;
-  checkup_launcher.LaunchCheckupInAccountWithActivity(
+  checkup_launcher.LaunchCheckupOnlineWithActivity(
       env,
       base::android::ConvertUTF8ToJavaString(
           env, password_manager::GetPasswordCheckupURL().spec()),
diff --git a/chrome/browser/password_manager/android/credential_leak_controller_android.cc b/chrome/browser/password_manager/android/credential_leak_controller_android.cc
index 607dd173..f36f550 100644
--- a/chrome/browser/password_manager/android/credential_leak_controller_android.cc
+++ b/chrome/browser/password_manager/android/credential_leak_controller_android.cc
@@ -70,7 +70,7 @@
       break;
     case LeakDialogType::kCheckup:
     case LeakDialogType::kCheckupAndChange:
-      checkup_launcher_->LaunchLocalCheckup(
+      checkup_launcher_->LaunchCheckupOnDevice(
           env, window_android_, PasswordCheckReferrerAndroid::kLeakDialog);
       break;
   }
diff --git a/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc b/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
index 108d114..ae6df9f3 100644
--- a/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
+++ b/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
@@ -134,7 +134,7 @@
       std::make_unique<MockPasswordCheckupLauncherHelper>();
   EXPECT_CALL(
       *mock_launcher,
-      LaunchLocalCheckup(
+      LaunchCheckupOnDevice(
           _, _, password_manager::PasswordCheckReferrerAndroid::kLeakDialog));
   MakeController(std::move(mock_launcher), IsSaved(true), IsReused(true),
                  IsSyncing(true))
@@ -165,7 +165,7 @@
       std::make_unique<MockPasswordCheckupLauncherHelper>();
   EXPECT_CALL(
       *mock_launcher,
-      LaunchLocalCheckup(
+      LaunchCheckupOnDevice(
           _, _, password_manager::PasswordCheckReferrerAndroid::kLeakDialog))
       .Times(0);
   MakeController(std::move(mock_launcher), IsSaved(true), IsReused(true),
diff --git a/chrome/browser/password_manager/android/mock_password_checkup_launcher_helper.h b/chrome/browser/password_manager/android/mock_password_checkup_launcher_helper.h
index db113400..50250c8 100644
--- a/chrome/browser/password_manager/android/mock_password_checkup_launcher_helper.h
+++ b/chrome/browser/password_manager/android/mock_password_checkup_launcher_helper.h
@@ -15,19 +15,19 @@
   MockPasswordCheckupLauncherHelper();
   ~MockPasswordCheckupLauncherHelper() override;
   MOCK_METHOD(void,
-              LaunchCheckupInAccountWithWindowAndroid,
+              LaunchCheckupOnlineWithWindowAndroid,
               (JNIEnv*,
                const base::android::JavaRef<jstring>&,
                const base::android::JavaRef<jobject>&),
               (override));
   MOCK_METHOD(void,
-              LaunchLocalCheckup,
+              LaunchCheckupOnDevice,
               (JNIEnv*,
                ui::WindowAndroid*,
                password_manager::PasswordCheckReferrerAndroid),
               (override));
   MOCK_METHOD(void,
-              LaunchCheckupInAccountWithActivity,
+              LaunchCheckupOnlineWithActivity,
               (JNIEnv*,
                const base::android::JavaRef<jstring>&,
                const base::android::JavaRef<jobject>&),
diff --git a/chrome/browser/password_manager/android/password_checkup_launcher_helper.h b/chrome/browser/password_manager/android/password_checkup_launcher_helper.h
index 973df39..fdc5aba 100644
--- a/chrome/browser/password_manager/android/password_checkup_launcher_helper.h
+++ b/chrome/browser/password_manager/android/password_checkup_launcher_helper.h
@@ -23,21 +23,21 @@
 
   virtual ~PasswordCheckupLauncherHelper() = 0;
 
-  // Launch the bulk password check in the Google Account
-  virtual void LaunchCheckupInAccountWithWindowAndroid(
+  // Launch the bulk password check in passwords.google.com
+  virtual void LaunchCheckupOnlineWithWindowAndroid(
       JNIEnv* env,
       const base::android::JavaRef<jstring>& checkupUrl,
       const base::android::JavaRef<jobject>& windowAndroid) = 0;
 
-  // Launch the bulk password check locally
-  virtual void LaunchLocalCheckup(
+  // Launch the bulk password check on device
+  virtual void LaunchCheckupOnDevice(
       JNIEnv* env,
       ui::WindowAndroid* windowAndroid,
       password_manager::PasswordCheckReferrerAndroid passwordCheckReferrer) = 0;
 
-  // Launch the bulk password check in the Google Account using an activity
+  // Launch the bulk password check in passwords.google.com using an activity
   // rather than a WindowAndroid
-  virtual void LaunchCheckupInAccountWithActivity(
+  virtual void LaunchCheckupOnlineWithActivity(
       JNIEnv* env,
       const base::android::JavaRef<jstring>& checkupUrl,
       const base::android::JavaRef<jobject>& activity) = 0;
diff --git a/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.cc b/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.cc
index 43c71a1..9f075eb4 100644
--- a/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.cc
+++ b/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.cc
@@ -9,30 +9,30 @@
 PasswordCheckupLauncherHelperImpl::~PasswordCheckupLauncherHelperImpl() =
     default;
 
-void PasswordCheckupLauncherHelperImpl::LaunchCheckupInAccountWithWindowAndroid(
+void PasswordCheckupLauncherHelperImpl::LaunchCheckupOnlineWithWindowAndroid(
     JNIEnv* env,
     const base::android::JavaRef<jstring>& checkupUrl,
     const base::android::JavaRef<jobject>& windowAndroid) {
-  Java_PasswordCheckupLauncher_launchCheckupInAccountWithWindowAndroid(
+  Java_PasswordCheckupLauncher_launchCheckupOnlineWithWindowAndroid(
       env, checkupUrl, windowAndroid);
 }
 
-void PasswordCheckupLauncherHelperImpl::LaunchLocalCheckup(
+void PasswordCheckupLauncherHelperImpl::LaunchCheckupOnDevice(
     JNIEnv* env,
     ui::WindowAndroid* windowAndroid,
     password_manager::PasswordCheckReferrerAndroid passwordCheckReferrer) {
   if (!windowAndroid) {
     return;
   }
-  Java_PasswordCheckupLauncher_launchLocalCheckup(
+  Java_PasswordCheckupLauncher_launchCheckupOnDevice(
       env, windowAndroid->GetJavaObject(),
       static_cast<int>(passwordCheckReferrer));
 }
 
-void PasswordCheckupLauncherHelperImpl::LaunchCheckupInAccountWithActivity(
+void PasswordCheckupLauncherHelperImpl::LaunchCheckupOnlineWithActivity(
     JNIEnv* env,
     const base::android::JavaRef<jstring>& checkupUrl,
     const base::android::JavaRef<jobject>& activity) {
-  Java_PasswordCheckupLauncher_launchCheckupInAccountWithActivity(
-      env, checkupUrl, activity);
+  Java_PasswordCheckupLauncher_launchCheckupOnlineWithActivity(env, checkupUrl,
+                                                               activity);
 }
diff --git a/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.h b/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.h
index e325c14..c65cb8e 100644
--- a/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.h
+++ b/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.h
@@ -23,21 +23,21 @@
 
   ~PasswordCheckupLauncherHelperImpl() override;
 
-  // Launch the bulk password check in the Google Account
-  void LaunchCheckupInAccountWithWindowAndroid(
+  // Launch the bulk password check in passwords.google.com
+  void LaunchCheckupOnlineWithWindowAndroid(
       JNIEnv* env,
       const base::android::JavaRef<jstring>& checkupUrl,
       const base::android::JavaRef<jobject>& windowAndroid) override;
 
-  // Launch the bulk password check locally
-  void LaunchLocalCheckup(JNIEnv* env,
-                          ui::WindowAndroid* window_android,
-                          password_manager::PasswordCheckReferrerAndroid
-                              passwordCheckReferrer) override;
+  // Launch the bulk password check on device
+  void LaunchCheckupOnDevice(JNIEnv* env,
+                             ui::WindowAndroid* window_android,
+                             password_manager::PasswordCheckReferrerAndroid
+                                 passwordCheckReferrer) override;
 
-  // Launch the bulk password check in the Google Account using an activity
+  // Launch the bulk password check in passwords.google.com using an activity
   // rather than a WindowAndroid
-  void LaunchCheckupInAccountWithActivity(
+  void LaunchCheckupOnlineWithActivity(
       JNIEnv* env,
       const base::android::JavaRef<jstring>& checkupUrl,
       const base::android::JavaRef<jobject>& activity) override;
diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc
index dff5883..c26bff9 100644
--- a/chrome/browser/permissions/chrome_permissions_client.cc
+++ b/chrome/browser/permissions/chrome_permissions_client.cc
@@ -34,6 +34,9 @@
 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
 #include "chrome/browser/subresource_filter/subresource_filter_profile_context_factory.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
+#include "chrome/browser/ui/hats/survey_config.h"
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
 #include "chrome/common/channel_info.h"
@@ -44,6 +47,7 @@
 #include "components/permissions/constants.h"
 #include "components/permissions/contexts/bluetooth_chooser_context.h"
 #include "components/permissions/features.h"
+#include "components/permissions/permission_hats_trigger_helper.h"
 #include "components/permissions/permission_request.h"
 #include "components/permissions/permission_uma_util.h"
 #include "components/permissions/permission_util.h"
@@ -73,10 +77,7 @@
 #include "components/permissions/permission_request_manager.h"
 #else
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/hats/hats_service.h"
-#include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/permission_bubble/permission_prompt.h"
-#include "components/permissions/permission_hats_trigger_helper.h"
 #include "components/vector_icons/vector_icons.h"
 #endif
 
@@ -261,11 +262,10 @@
   return PermissionsClient::GetOverrideIconId(request_type);
 }
 
-#if !BUILDFLAG(IS_ANDROID)
 // Triggers the prompt HaTS survey if enabled by field trials for this
 // combination of prompt parameters.
 void ChromePermissionsClient::TriggerPromptHatsSurveyIfEnabled(
-    content::BrowserContext* context,
+    content::WebContents* web_contents,
     permissions::RequestType request_type,
     absl::optional<permissions::PermissionAction> action,
     permissions::PermissionPromptDisposition prompt_disposition,
@@ -275,7 +275,8 @@
     bool is_post_prompt,
     const GURL& gurl,
     base::OnceCallback<void()> hats_shown_callback) {
-  Profile* profile = Profile::FromBrowserContext(context);
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
   absl::optional<GURL> recorded_gurl =
       profile->GetPrefs()->GetBoolean(
           unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled)
@@ -315,12 +316,13 @@
   auto survey_data = permissions::PermissionHatsTriggerHelper::
       SurveyProductSpecificData::PopulateFrom(prompt_parameters);
 
-  hats_service->LaunchSurvey(trigger_and_probability->first,
-                             std::move(hats_shown_callback), base::DoNothing(),
-                             survey_data.survey_bits_data,
-                             survey_data.survey_string_data);
+  hats_service->LaunchSurveyForWebContents(
+      trigger_and_probability->first, web_contents,
+      survey_data.survey_bits_data, survey_data.survey_string_data,
+      std::move(hats_shown_callback), base::DoNothing());
 }
 
+#if !BUILDFLAG(IS_ANDROID)
 permissions::PermissionIgnoredReason
 ChromePermissionsClient::DetermineIgnoreReason(
     content::WebContents* web_contents) {
@@ -384,7 +386,6 @@
     }
   }
 
-#if !BUILDFLAG(IS_ANDROID)
   auto content_setting_type = RequestTypeToContentSettingsType(request_type);
   if (content_setting_type.has_value()) {
     permissions::PermissionHatsTriggerHelper::
@@ -393,12 +394,10 @@
   }
 
   TriggerPromptHatsSurveyIfEnabled(
-      web_contents->GetBrowserContext(), request_type,
-      absl::make_optional(action), prompt_disposition,
-      prompt_disposition_reason, gesture_type,
+      web_contents, request_type, absl::make_optional(action),
+      prompt_disposition, prompt_disposition_reason, gesture_type,
       absl::make_optional(prompt_display_duration), true,
       web_contents->GetLastCommittedURL(), base::DoNothing());
-#endif  // !BUILDFLAG(IS_ANDROID)
 }
 
 absl::optional<bool>
diff --git a/chrome/browser/permissions/chrome_permissions_client.h b/chrome/browser/permissions/chrome_permissions_client.h
index 57c7368d..1c6115e 100644
--- a/chrome/browser/permissions/chrome_permissions_client.h
+++ b/chrome/browser/permissions/chrome_permissions_client.h
@@ -58,9 +58,8 @@
   CreatePermissionUiSelectors(
       content::BrowserContext* browser_context) override;
 
-#if !BUILDFLAG(IS_ANDROID)
   void TriggerPromptHatsSurveyIfEnabled(
-      content::BrowserContext* context,
+      content::WebContents* web_contents,
       permissions::RequestType request_type,
       absl::optional<permissions::PermissionAction> action,
       permissions::PermissionPromptDisposition prompt_disposition,
@@ -71,6 +70,7 @@
       const GURL& gurl,
       base::OnceCallback<void()> hats_shown_callback_) override;
 
+#if !BUILDFLAG(IS_ANDROID)
   permissions::PermissionIgnoredReason DetermineIgnoreReason(
       content::WebContents* web_contents) override;
 #endif
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 574f09d..2c72490 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -76,7 +76,7 @@
 #include "chrome/browser/tpcd/experiment/tpcd_pref_names.h"
 #include "chrome/browser/tracing/chrome_tracing_delegate.h"
 #include "chrome/browser/ui/browser_ui_prefs.h"
-#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_desktop.h"
 #include "chrome/browser/ui/network_profile_bubble.h"
 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
 #include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
@@ -138,6 +138,7 @@
 #include "components/password_manager/core/browser/password_manager.h"
 #include "components/payments/core/payment_prefs.h"
 #include "components/performance_manager/public/user_tuning/prefs.h"
+#include "components/permissions/permission_hats_trigger_helper.h"
 #include "components/permissions/pref_names.h"
 #include "components/plus_addresses/plus_address_prefs.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
@@ -306,7 +307,6 @@
 #include "components/live_caption/live_caption_controller.h"
 #include "components/live_caption/live_translate_controller.h"
 #include "components/ntp_tiles/custom_links_manager_impl.h"
-#include "components/permissions/permission_hats_trigger_helper.h"
 #include "components/user_notes/user_notes_prefs.h"
 #endif  // BUILDFLAG(IS_ANDROID)
 
@@ -973,6 +973,12 @@
 constexpr char kSystemTrayExpanded[] = "ash.system_tray.expanded";
 #endif
 
+// Deprecated 11/2023.
+constexpr char kPasswordChangeSuccessTrackerFlows[] =
+    "password_manager.password_change_success_tracker.flows";
+constexpr char kPasswordChangeSuccessTrackerVersion[] =
+    "password_manager.password_change_success_tracker.version";
+
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
@@ -1380,6 +1386,10 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   registry->RegisterBooleanPref(kUserGeolocationAllowed, true);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+  // Deprecated 11/2023.
+  registry->RegisterListPref(kPasswordChangeSuccessTrackerFlows);
+  registry->RegisterIntegerPref(kPasswordChangeSuccessTrackerVersion, 0);
 }
 
 void ClearSyncRequestedPrefAndMaybeMigrate(PrefService* profile_prefs) {
@@ -1685,6 +1695,7 @@
   dom_distiller::DistilledPagePrefs::RegisterProfilePrefs(registry);
   dom_distiller::RegisterProfilePrefs(registry);
   DownloadPrefs::RegisterProfilePrefs(registry);
+  permissions::PermissionHatsTriggerHelper::RegisterProfilePrefs(registry);
   history_clusters::prefs::RegisterProfilePrefs(registry);
   HostContentSettingsMap::RegisterProfilePrefs(registry);
   image_fetcher::ImageCache::RegisterProfilePrefs(registry);
@@ -1837,7 +1848,7 @@
   extensions::TabsCaptureVisibleTabFunction::RegisterProfilePrefs(registry);
   first_run::RegisterProfilePrefs(registry);
   gcm::RegisterProfilePrefs(registry);
-  HatsService::RegisterProfilePrefs(registry);
+  HatsServiceDesktop::RegisterProfilePrefs(registry);
   NtpCustomBackgroundService::RegisterProfilePrefs(registry);
   media_router::RegisterAccessCodeProfilePrefs(registry);
   media_router::RegisterProfilePrefs(registry);
@@ -2046,7 +2057,6 @@
   registry->RegisterIntegerPref(prefs::kHighEfficiencyChipExpandedCount, 0);
   registry->RegisterTimePref(prefs::kLastHighEfficiencyChipExpandedTimestamp,
                              base::Time());
-  permissions::PermissionHatsTriggerHelper::RegisterProfilePrefs(registry);
 #endif
 
 #if BUILDFLAG(IS_ANDROID)
@@ -2580,6 +2590,10 @@
   profile_prefs->ClearPref(kUserGeolocationAllowed);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+  // Deprecated 11/2023.
+  profile_prefs->ClearPref(kPasswordChangeSuccessTrackerFlows);
+  profile_prefs->ClearPref(kPasswordChangeSuccessTrackerVersion);
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 
diff --git a/chrome/browser/profile_resetter/resettable_settings_snapshot.cc b/chrome/browser/profile_resetter/resettable_settings_snapshot.cc
index b897e97..b7783f1 100644
--- a/chrome/browser/profile_resetter/resettable_settings_snapshot.cc
+++ b/chrome/browser/profile_resetter/resettable_settings_snapshot.cc
@@ -175,8 +175,20 @@
       new reset_report::ChromeResetReport());
 
   if (field_mask & ResettableSettingsSnapshot::STARTUP_MODE) {
-    for (const auto& url : snapshot.startup_urls())
-      report->add_startup_url_path(url.spec());
+    for (const auto& url : snapshot.startup_urls()) {
+      // TODO(crbug.com/1501983) Delete the following if-block once we have
+      // seen some samples.
+      if (!url.is_valid()) {
+        // This is intentionally a DUMP_WILL_BE_NOTREACHED_NORETURN() instead of
+        // a DCHECK() so that it generates crash dumps with debug data. A
+        // similar NOTREACHED() used to be triggered by url.spec() and the
+        // volume is not incredibly high. A DCHECK() would probably not be
+        // sufficient because we had very few cases of url.is_valid() being
+        // false on the pre-stable channels.
+        DUMP_WILL_BE_NOTREACHED_NORETURN() << url.possibly_invalid_spec();
+      }
+      report->add_startup_url_path(url.is_valid() ? url.spec() : std::string());
+    }
     switch (snapshot.startup_type()) {
       case SessionStartupPref::DEFAULT:
         report->set_startup_type(
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 53fbc78f..839daf21 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -187,6 +187,7 @@
 #include "chrome/browser/translate/translate_ranker_factory.h"
 #include "chrome/browser/ui/cookie_controls/cookie_controls_service_factory.h"
 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/media_router/cast_notification_controller_lacros_factory.h"
 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
 #include "chrome/browser/ui/tabs/pinned_tab_service_factory.h"
@@ -288,6 +289,7 @@
 #include "chrome/browser/apps/app_service/subscriber_crosapi_factory.h"
 #include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
 #include "chrome/browser/ash/browser_context_keyed_service_factories.h"
+#include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/file_manager/cloud_upload_prefs_watcher.h"
 #include "chrome/browser/ash/input_method/editor_mediator_factory.h"
 #include "chrome/browser/ash/policy/dlp/files_policy_notification_manager_factory.h"
@@ -303,6 +305,7 @@
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/certificate_provider/certificate_provider_service_factory.h"
 #include "chrome/browser/chromeos/cros_apps/cros_apps_key_event_handler_factory.h"
+#include "chrome/browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_download_observer_factory.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h"
 #include "chrome/browser/policy/messaging_layer/util/manual_test_heartbeat_event_factory.h"
@@ -680,6 +683,21 @@
   ChromeSigninClientFactory::GetInstance();
   ClientHintsFactory::GetInstance();
   ClipboardRestrictionServiceFactory::GetInstance();
+// The keyed service must run on the same side (ash / lacros) as the odfs
+// extension.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  if (chromeos::features::
+          IsMicrosoftOneDriveIntegrationForEnterpriseEnabled()) {
+    chromeos::cloud_storage::OneDrivePrefObserverFactory::GetInstance();
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (chromeos::features::
+          IsMicrosoftOneDriveIntegrationForEnterpriseEnabled() &&
+      !crosapi::browser_util::IsLacrosEnabled()) {
+    chromeos::cloud_storage::OneDrivePrefObserverFactory::GetInstance();
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 #if !BUILDFLAG(IS_ANDROID)
   ClosedTabCacheServiceFactory::GetInstance();
   companion::visual_search::VisualSearchSuggestionsServiceFactory::
@@ -930,6 +948,7 @@
 #endif
   ProfilePasswordStoreFactory::GetInstance();
   payments::CanMakePaymentQueryFactory::GetInstance();
+  HatsServiceFactory::GetInstance();
 #if !BUILDFLAG(IS_ANDROID)
   payments::PaymentRequestDisplayManagerFactory::GetInstance();
   performance_manager::SiteDataCacheFacadeFactory::GetInstance();
diff --git a/chrome/browser/resources/chromeos/login/components/common_styles/cr_card_radio_group_styles.css b/chrome/browser/resources/chromeos/login/components/common_styles/cr_card_radio_group_styles.css
index 6ed9555..5a10fd4 100644
--- a/chrome/browser/resources/chromeos/login/components/common_styles/cr_card_radio_group_styles.css
+++ b/chrome/browser/resources/chromeos/login/components/common_styles/cr_card_radio_group_styles.css
@@ -27,20 +27,13 @@
     * corners, which don't match the rounded corners of the card. */
     -webkit-tap-highlight-color: transparent;
     border-radius: 16px;
-    height: var(--radio-button-height);
     margin-bottom: 0;
-    --cr-button-edge-spacing: 0;
     --cr-card-radio-button-checkmark-right: 24px;
 
-    --cr-card-radio-button-checkmark-top:
-        calc(var(--radio-button-height) / 2 - 12px);
-    --cr-card-radio-button-height: var(--radio-button-height);
+    --cr-card-radio-button-checkmark-top: calc(50% - 12px);
     --cr-card-radio-button-margin: 0;
     --cr-card-radio-button-padding: 0;
     --cr-card-radio-button-width: 100%;
-    /* TODO(https://crbug.com/1320715) Revise the color
-    --cr-checked-color: var(--google-blue-500);
-    */
     --cr-radio-button-ink-size: 0;
     --cr-radio-group-item-padding: 0;
 }
@@ -62,11 +55,10 @@
     align-items: center;
     display: flex;
     flex-direction: row;
-    /* Same height to center button content in vertical dimension. */
-    height: var(--radio-button-height);
+    min-height: var(--radio-button-height);
     padding-bottom: 0;
-    padding-inline-end: 48px;
-    padding-inline-start: 24px;
+    padding-inline-end: 44px;
+    padding-inline-start: 20px;
     padding-top: 0;
     text-align: start;
 }
@@ -82,8 +74,10 @@
 }
 
 .card-content {
-    padding-inline-end: 24px;
-    padding-inline-start: 24px;
+    padding-bottom: 20px;
+    padding-inline-end: 20px;
+    padding-inline-start: 20px;
+    padding-top: 20px;
 }
 
 .card-label {
diff --git a/chrome/browser/resources/extensions/detail_view.ts b/chrome/browser/resources/extensions/detail_view.ts
index e84c9994..5c4c23c 100644
--- a/chrome/browser/resources/extensions/detail_view.ts
+++ b/chrome/browser/resources/extensions/detail_view.ts
@@ -415,7 +415,12 @@
     if (!loadTimeData.getBoolean('safetyCheckShowReviewPanel')) {
       return false;
     }
-
+    const ExtensionType = chrome.developerPrivate.ExtensionType;
+    // Check to make sure this is an extension and not a Chrome app.
+    if (!(this.data.type === ExtensionType.EXTENSION ||
+          this.data.type === ExtensionType.SHARED_MODULE)) {
+      return false;
+    }
     return !!(
         this.data.safetyCheckText && this.data.safetyCheckText.detailString &&
         this.data.acknowledgeSafetyCheckWarning !== true);
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service.cc b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
index 332628f..e5131af 100644
--- a/chrome/browser/safe_browsing/chrome_password_protection_service.cc
+++ b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
@@ -1050,7 +1050,7 @@
 #if BUILDFLAG(IS_ANDROID)
     JNIEnv* env = base::android::AttachCurrentThread();
     PasswordCheckupLauncherHelperImpl checkup_launcher;
-    checkup_launcher.LaunchLocalCheckup(
+    checkup_launcher.LaunchCheckupOnDevice(
         env, web_contents->GetTopLevelNativeWindow(),
         password_manager::PasswordCheckReferrerAndroid::kPhishedWarningDialog);
 #endif
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 4026374..a1bfc289 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -114,6 +114,10 @@
     "find_bar/find_bar_state.h",
     "find_bar/find_bar_state_factory.cc",
     "find_bar/find_bar_state_factory.h",
+    "hats/hats_service.cc",
+    "hats/hats_service.h",
+    "hats/hats_service_factory.cc",
+    "hats/hats_service_factory.h",
     "hats/survey_config.cc",
     "hats/survey_config.h",
     "idle_bubble.h",
@@ -828,6 +832,8 @@
       "android/fast_checkout/fast_checkout_view_impl.h",
       "android/fast_checkout/ui_view_android_utils.cc",
       "android/fast_checkout/ui_view_android_utils.h",
+      "android/hats/hats_service_android.cc",
+      "android/hats/hats_service_android.h",
       "android/hats/survey_client_android.cc",
       "android/hats/survey_client_android.h",
       "android/hats/survey_config_android.cc",
@@ -1216,10 +1222,8 @@
       "global_media_controls/supplemental_device_picker_producer.h",
       "hats/hats_helper.cc",
       "hats/hats_helper.h",
-      "hats/hats_service.cc",
-      "hats/hats_service.h",
-      "hats/hats_service_factory.cc",
-      "hats/hats_service_factory.h",
+      "hats/hats_service_desktop.cc",
+      "hats/hats_service_desktop.h",
       "hats/trust_safety_sentiment_service.cc",
       "hats/trust_safety_sentiment_service.h",
       "hats/trust_safety_sentiment_service_factory.cc",
@@ -4372,6 +4376,8 @@
       "find_bar/find_bar_platform_helper_mac.mm",
       "fullscreen_util_mac.cc",
       "fullscreen_util_mac.h",
+      "passwords/bubble_controllers/relaunch_chrome_bubble_controller.cc",
+      "passwords/bubble_controllers/relaunch_chrome_bubble_controller.h",
       "views/apps/chrome_app_window_client_views_mac.mm",
       "views/certificate_viewer_mac_views.mm",
       "views/dropdown_bar_host_mac.mm",
@@ -4918,8 +4924,6 @@
       "views/autofill/popup/popup_row_content_view.h",
       "views/autofill/popup/popup_row_factory_utils.cc",
       "views/autofill/popup/popup_row_factory_utils.h",
-      "views/autofill/popup/popup_row_strategy.cc",
-      "views/autofill/popup/popup_row_strategy.h",
       "views/autofill/popup/popup_row_view.cc",
       "views/autofill/popup/popup_row_view.h",
       "views/autofill/popup/popup_row_with_button_view.cc",
diff --git a/chrome/browser/ui/android/hats/hats_service_android.cc b/chrome/browser/ui/android/hats/hats_service_android.cc
new file mode 100644
index 0000000..459c182
--- /dev/null
+++ b/chrome/browser/ui/android/hats/hats_service_android.cc
@@ -0,0 +1,238 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/android/hats/hats_service_android.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/functional/bind.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/notreached.h"
+#include "base/ranges/algorithm.h"
+#include "base/task/sequenced_task_runner.h"
+#include "chrome/browser/android/resource_mapper.h"
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profiles_state.h"
+#include "chrome/browser/sessions/exit_type_service.h"
+#include "chrome/browser/ui/android/hats/survey_client_android.h"
+#include "chrome/browser/ui/android/hats/survey_ui_delegate_android.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/messages/android/message_wrapper.h"
+#include "components/resources/android/theme_resources.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/l10n/l10n_util.h"
+
+constexpr char kHatsShouldShowSurveyReasonAndroidHistogram[] =
+    "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReasonAndroid";
+
+HatsServiceAndroid::DelayedSurveyTask::DelayedSurveyTask(
+    HatsServiceAndroid* hats_service,
+    const std::string& trigger,
+    content::WebContents* web_contents,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback)
+    : web_contents_(web_contents),
+      hats_service_(hats_service),
+      trigger_(trigger),
+      product_specific_bits_data_(product_specific_bits_data),
+      product_specific_string_data_(product_specific_string_data),
+      success_callback_(std::move(success_callback)),
+      failure_callback_(std::move(failure_callback)) {}
+
+HatsServiceAndroid::DelayedSurveyTask::~DelayedSurveyTask() = default;
+
+void HatsServiceAndroid::DelayedSurveyTask::Launch() {
+  CHECK(web_contents());
+  if (!web_contents() ||
+      web_contents()->GetVisibility() != content::Visibility::VISIBLE) {
+    return;
+  }
+
+  message_ = std::make_unique<messages::MessageWrapper>(
+      messages::MessageIdentifier::CHROME_SURVEY, std::move(success_callback_),
+      base::BindOnce(&HatsServiceAndroid::DelayedSurveyTask::DismissCallback,
+                     weak_ptr_factory_.GetWeakPtr()));
+
+  hats::SurveyUiDelegateAndroid delegate(
+      message_.get(), web_contents()->GetTopLevelNativeWindow());
+
+  // Create survey client with delegate.
+  hats::SurveyClientAndroid survey_client(trigger_, &delegate,
+                                          hats_service_->profile());
+  survey_client.LaunchSurvey(web_contents()->GetTopLevelNativeWindow(),
+                             product_specific_bits_data_,
+                             product_specific_string_data_);
+}
+
+void HatsServiceAndroid::DelayedSurveyTask::DismissCallback(
+    messages::DismissReason dismiss_reason) {
+  if (dismiss_reason != messages::DismissReason::PRIMARY_ACTION &&
+      !failure_callback_.is_null()) {
+    std::move(failure_callback_).Run();
+  }
+
+  ShouldShowSurveyReasonsAndroid reason;
+  switch (dismiss_reason) {
+    case messages::DismissReason::UNKNOWN:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidUnknown;
+      break;
+    case messages::DismissReason::PRIMARY_ACTION:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidAccepted;
+      break;
+    case messages::DismissReason::SECONDARY_ACTION:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidSecondaryAction;
+      break;
+    case messages::DismissReason::TIMER:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidExpired;
+      break;
+    case messages::DismissReason::GESTURE:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidDismissedByGesture;
+      break;
+    case messages::DismissReason::TAB_SWITCHED:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidTabSwitched;
+      break;
+    case messages::DismissReason::TAB_DESTROYED:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidTabDestroyed;
+      break;
+    case messages::DismissReason::ACTIVITY_DESTROYED:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidActivityDestroyed;
+      break;
+    case messages::DismissReason::SCOPE_DESTROYED:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidScopeDestroyed;
+      break;
+    case messages::DismissReason::DISMISSED_BY_FEATURE:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidDismissedByFeature;
+      break;
+    case messages::DismissReason::COUNT:
+      reason = ShouldShowSurveyReasonsAndroid::kAndroidUnknown;
+      NOTREACHED();
+  }
+  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonAndroidHistogram,
+                            reason);
+  hats_service_->RemoveTask(*this);
+}
+
+base::WeakPtr<HatsServiceAndroid::DelayedSurveyTask>
+HatsServiceAndroid::DelayedSurveyTask::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+HatsServiceAndroid::HatsServiceAndroid(Profile* profile)
+    : HatsService(profile) {}
+
+HatsServiceAndroid::~HatsServiceAndroid() = default;
+
+void HatsServiceAndroid::LaunchSurvey(
+    const std::string& trigger,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  NOTIMPLEMENTED();
+}
+
+void HatsServiceAndroid::LaunchSurveyForWebContents(
+    const std::string& trigger,
+    content::WebContents* web_contents,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback) {
+  // By using a delayed survey with a delay of 0, we can centralize the object
+  // lifecycle management duties for native clank survey triggers.
+  LaunchDelayedSurveyForWebContents(
+      trigger, web_contents, 0, product_specific_bits_data,
+      product_specific_string_data, false, std::move(success_callback),
+      std::move(failure_callback));
+}
+
+bool HatsServiceAndroid::LaunchDelayedSurvey(
+    const std::string& trigger,
+    int timeout_ms,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+bool HatsServiceAndroid::LaunchDelayedSurveyForWebContents(
+    const std::string& trigger,
+    content::WebContents* web_contents,
+    int timeout_ms,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data,
+    bool require_same_origin,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback) {
+  CHECK(web_contents);
+  CHECK(!require_same_origin);  // Currently not supported on Android
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (survey_configs_by_triggers_.find(trigger) ==
+      survey_configs_by_triggers_.end()) {
+    // Survey configuration is not available.
+    if (!failure_callback.is_null()) {
+      std::move(failure_callback).Run();
+    }
+    return false;
+  }
+  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));
+  if (!result.second) {
+    return false;
+  }
+  auto success =
+      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+          FROM_HERE,
+          base::BindOnce(&HatsServiceAndroid::DelayedSurveyTask::Launch,
+                         const_cast<HatsServiceAndroid::DelayedSurveyTask&>(
+                             *(result.first))
+                             .GetWeakPtr()),
+          base::Milliseconds(timeout_ms));
+  if (!success) {
+    pending_tasks_.erase(result.first);
+  }
+  return success;
+}
+
+bool HatsServiceAndroid::CanShowAnySurvey(bool user_prompted) const {
+  NOTIMPLEMENTED();  // Survey throttling happens on the clank side
+  return false;
+}
+
+bool HatsServiceAndroid::CanShowSurvey(const std::string& trigger) const {
+  NOTIMPLEMENTED();  // Survey throttling happens on the clank side
+  return false;
+}
+
+void HatsServiceAndroid::RecordSurveyAsShown(std::string trigger_id) {
+  // Record the trigger associated with the trigger_id. This is recorded
+  // instead of the trigger ID itself, as the ID is specific to individual
+  // survey versions. There should be a cooldown before a user is prompted to
+  // take a survey from the same trigger, regardless of whether the survey was
+  // updated.
+  auto trigger_survey_config =
+      base::ranges::find(survey_configs_by_triggers_, trigger_id,
+                         [](const SurveyConfigs::value_type& pair) {
+                           return pair.second.trigger_id;
+                         });
+
+  DCHECK(trigger_survey_config != survey_configs_by_triggers_.end());
+  std::string trigger = trigger_survey_config->first;
+
+  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonAndroidHistogram,
+                            ShouldShowSurveyReasonsAndroid::kYes);
+}
+
+void HatsServiceAndroid::RemoveTask(const DelayedSurveyTask& task) {
+  pending_tasks_.erase(task);
+}
diff --git a/chrome/browser/ui/android/hats/hats_service_android.h b/chrome/browser/ui/android/hats/hats_service_android.h
new file mode 100644
index 0000000..1a8512cd
--- /dev/null
+++ b/chrome/browser/ui/android/hats/hats_service_android.h
@@ -0,0 +1,169 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ANDROID_HATS_HATS_SERVICE_ANDROID_H_
+#define CHROME_BROWSER_UI_ANDROID_HATS_HATS_SERVICE_ANDROID_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/functional/callback.h"
+#include "base/functional/callback_forward.h"
+#include "base/functional/callback_helpers.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "components/messages/android/message_enums.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace messages {
+class MessageWrapper;
+}
+
+// The name of the histogram which records if a survey was shown, or if not, the
+// reason why not.
+extern const char kHatsShouldShowSurveyReasonAndroidHistogram[];
+
+// This class provides the client side logic for determining if a
+// survey should be shown for any trigger based on input from a finch
+// configuration. It is created on a per profile basis.
+class HatsServiceAndroid : public HatsService {
+ public:
+  class DelayedSurveyTask {
+   public:
+    DelayedSurveyTask(HatsServiceAndroid* hats_service,
+                      const std::string& trigger,
+                      content::WebContents* web_contents,
+                      const SurveyBitsData& product_specific_bits_data,
+                      const SurveyStringData& product_specific_string_data,
+                      base::OnceClosure success_callback,
+                      base::OnceClosure failure_callback);
+
+    // Not copyable or movable
+    DelayedSurveyTask(const DelayedSurveyTask&) = delete;
+    DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;
+
+    ~DelayedSurveyTask();
+
+    // Asks |hats_service_| to launch the survey with id |trigger_| for tab
+    // |web_contents_|.
+    void Launch();
+
+    void DismissCallback(messages::DismissReason reason);
+
+    content::WebContents* web_contents() const { return web_contents_.get(); }
+
+    // Returns a weak pointer to this object.
+    base::WeakPtr<DelayedSurveyTask> GetWeakPtr();
+
+    bool operator<(const HatsServiceAndroid::DelayedSurveyTask& other) const {
+      return trigger_ < other.trigger_ ? true
+                                       : web_contents() < other.web_contents();
+    }
+
+    messages::MessageWrapper* GetMessageForTesting() { return message_.get(); }
+
+   private:
+    raw_ptr<content::WebContents> web_contents_;
+    raw_ptr<HatsServiceAndroid> hats_service_;
+
+    std::unique_ptr<messages::MessageWrapper> message_;
+    std::string trigger_;
+    SurveyBitsData product_specific_bits_data_;
+    SurveyStringData product_specific_string_data_;
+    base::OnceClosure success_callback_;
+    base::OnceClosure failure_callback_;
+    base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
+  };
+
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class ShouldShowSurveyReasonsAndroid {
+    kYes = 0,
+    kAndroidUnknown = 1,   // Catch all for Android invitation dismissals.
+                           // Should be investigated if this regularly occurs.
+    kAndroidAccepted = 2,  // Invitation accepted
+    kAndroidSecondaryAction =
+        3,  // Not in use by the default survey implementation. May be used by
+            // customized trigger implementations.
+    kAndroidExpired = 4,  // Survey invitation expired and was automatically
+                          // dismissed. Default timeout is 10s, see
+                          // `ChromeMessageAutodismissDurationProvider.java`
+    kAndroidDismissedByGesture = 5,  // Dismissed by swiping the dialog away
+    kAndroidTabSwitched = 6,
+    kAndroidTabDestroyed = 7,
+    kAndroidActivityDestroyed = 8,
+    kAndroidScopeDestroyed = 9,
+    kAndroidDismissedByFeature =
+        10,  // Another survey was already launched, leading to the current one
+             // being aborted.
+    kMaxValue = kAndroidDismissedByFeature,
+  };
+
+  explicit HatsServiceAndroid(Profile* profile);
+
+  HatsServiceAndroid(const HatsServiceAndroid&) = delete;
+  HatsServiceAndroid& operator=(const HatsServiceAndroid&) = delete;
+
+  ~HatsServiceAndroid() override;
+
+  void LaunchSurvey(
+      const std::string& trigger,
+      base::OnceClosure success_callback = base::DoNothing(),
+      base::OnceClosure failure_callback = base::DoNothing(),
+      const SurveyBitsData& product_specific_bits_data = {},
+      const SurveyStringData& product_specific_string_data = {}) override;
+
+  void LaunchSurveyForWebContents(
+      const std::string& trigger,
+      content::WebContents* web_contents,
+      const SurveyBitsData& product_specific_bits_data,
+      const SurveyStringData& product_specific_string_data,
+      base::OnceClosure success_callback = base::DoNothing(),
+      base::OnceClosure failure_callback = base::DoNothing()) override;
+
+  bool LaunchDelayedSurvey(
+      const std::string& trigger,
+      int timeout_ms,
+      const SurveyBitsData& product_specific_bits_data = {},
+      const SurveyStringData& product_specific_string_data = {}) override;
+
+  bool LaunchDelayedSurveyForWebContents(
+      const std::string& trigger,
+      content::WebContents* web_contents,
+      int timeout_ms,
+      const SurveyBitsData& product_specific_bits_data = {},
+      const SurveyStringData& product_specific_string_data = {},
+      bool require_same_origin = false,
+      base::OnceClosure success_callback = base::DoNothing(),
+      base::OnceClosure failure_callback = base::DoNothing()) override;
+
+  // Currently not implemented
+  bool CanShowAnySurvey(bool user_prompted) const override;
+
+  // Currently not implemented
+  bool CanShowSurvey(const std::string& trigger) const override;
+
+  void RecordSurveyAsShown(std::string trigger_id) override;
+
+  DelayedSurveyTask& GetFirstTaskForTesting() {
+    return const_cast<DelayedSurveyTask&>(*pending_tasks_.begin());
+  }
+
+ protected:
+  // Remove |task| from the set of |pending_tasks_|.
+  void RemoveTask(const DelayedSurveyTask& task);
+
+ private:
+  friend class DelayedSurveyTask;
+  FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
+
+  std::set<DelayedSurveyTask> pending_tasks_;
+  base::WeakPtrFactory<HatsServiceAndroid> weak_ptr_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_UI_ANDROID_HATS_HATS_SERVICE_ANDROID_H_
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java
index c3aeed2..1204fc6f 100644
--- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java
+++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyThrottler.java
@@ -106,7 +106,7 @@
 
     /**
      * Rolls a random number to see if the user was eligible for the survey. The user will skip the
-     * roll if: 1. User is a first time user; 2. User has performed the roll for the survey today; 
+     * roll if: 1. User is a first time user; 2. User has performed the roll for the survey today;
      * 3. Max number is not setup correctly.
      *
      * @return Whether the user is eligible (i.e. the random number rolled was 0).
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 16dc7df1..1d4989a 100644
--- a/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
+++ b/chrome/browser/ui/android/hats/survey_client_android_browsertest.cc
@@ -6,18 +6,15 @@
 
 #include "base/functional/bind.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/android/hats/hats_service_android.h"
 #include "chrome/browser/ui/android/hats/survey_client_android.h"
-#include "chrome/browser/ui/android/hats/survey_ui_delegate_android.h"
 #include "chrome/browser/ui/android/hats/test/test_survey_utils_bridge.h"
+#include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/test/base/chrome_test_utils.h"
 #include "components/messages/android/message_dispatcher_bridge.h"
-#include "components/messages/android/message_wrapper.h"
 #include "components/messages/android/test/messages_test_helper.h"
 #include "content/public/test/browser_test.h"
 #include "net/dns/mock_host_resolver.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/android/window_android.h"
 
 namespace hats {
 namespace {
@@ -45,6 +42,29 @@
 
   content::WaiterHelper waiter_helper_;
 };
+
+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 SurveyClientAndroidBrowserTest : public AndroidBrowserTest {
@@ -77,63 +97,39 @@
     return web_contents()->GetTopLevelNativeWindow();
   }
 
-  messages::MessageWrapper* createMessage() {
-    message_ = std::make_unique<messages::MessageWrapper>(
-        messages::MessageIdentifier::TEST_MESSAGE,
-        base::BindOnce(&SurveyClientAndroidBrowserTest::MessageAccepted,
-                       base::Unretained(this)),
-        base::BindOnce(&SurveyClientAndroidBrowserTest::MessageDeclined,
-                       base::Unretained(this)));
-    return message_.get();
+  HatsServiceAndroid* GetHatsService() {
+    HatsServiceAndroid* service =
+        static_cast<HatsServiceAndroid*>(HatsServiceFactory::GetForProfile(
+            Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
+            true));
+    return service;
   }
 
-  bool message_accepted() { return message_accepted_; }
-
-  bool message_declined() { return message_declined_; }
-
  protected:
-  void MessageAccepted() { message_accepted_ = true; }
-
-  void MessageDeclined(messages::DismissReason dismiss_reason) {
-    message_declined_ = true;
-  }
-
   messages::MessagesTestHelper messages_test_helper_;
-
- private:
-  std::unique_ptr<messages::MessageWrapper> message_;
-  bool message_accepted_;
-  bool message_declined_;
 };
 
-IN_PROC_BROWSER_TEST_F(SurveyClientAndroidBrowserTest,
-                       CreateSurveyWithMessage) {
+IN_PROC_BROWSER_TEST_F(SurveyClientAndroidBrowserTest, LaunchSurvey) {
   EXPECT_TRUE(content::WaitForLoadStop(web_contents()));
-  auto* message = createMessage();
-  std::unique_ptr<SurveyUiDelegateAndroid> delegate =
-      std::make_unique<SurveyUiDelegateAndroid>(message, window_android());
-  EXPECT_TRUE(delegate.get());
 
-  // Create survey client with delegate.
-  std::unique_ptr<SurveyClientAndroid> survey_client =
-      std::make_unique<SurveyClientAndroid>(
-          kTestSurveyTrigger, delegate.get(),
-          ProfileManager::GetActiveUserProfile());
+  SurveyObserver observer;
+
+  auto* hatsService = GetHatsService();
   {
     MessageWaiter waiter(messages_test_helper_);
-    survey_client->LaunchSurvey(window_android(),
-                                kTestSurveyProductSpecificBitsData,
-                                kTestSurveyProductSpecificStringData);
+    hatsService->LaunchSurveyForWebContents(
+        kTestSurveyTrigger, web_contents(), kTestSurveyProductSpecificBitsData,
+        kTestSurveyProductSpecificStringData,
+        base::BindOnce(&SurveyObserver::Accept, observer.GetWeakPtr()),
+        base::BindOnce(&SurveyObserver::Dismiss, observer.GetWeakPtr()));
     EXPECT_TRUE(waiter.Wait());
   }
 
-  EXPECT_EQ(1, messages_test_helper_.GetMessageCount(window_android()));
-  EXPECT_EQ(static_cast<int>(messages::MessageIdentifier::TEST_MESSAGE),
-            messages_test_helper_.GetMessageIdentifier(window_android(), 0));
+  hatsService->GetFirstTaskForTesting()
+      .GetMessageForTesting()
+      ->HandleActionClick(base::android::AttachCurrentThread());
 
-  messages::MessageDispatcherBridge::Get()->DismissMessage(
-      message, messages::DismissReason::UNKNOWN);
-  EXPECT_TRUE(message_declined());
+  EXPECT_TRUE(observer.IsAccepted());
 }
 
 }  // namespace hats
diff --git a/chrome/browser/ui/ash/in_session_auth_token_provider_impl.cc b/chrome/browser/ui/ash/in_session_auth_token_provider_impl.cc
index 2181fe8..d5bf928 100644
--- a/chrome/browser/ui/ash/in_session_auth_token_provider_impl.cc
+++ b/chrome/browser/ui/ash/in_session_auth_token_provider_impl.cc
@@ -9,9 +9,7 @@
 #include "ash/shell.h"
 #include "base/check.h"
 #include "base/time/time.h"
-#include "chrome/browser/ash/login/quick_unlock/auth_token.h"
-#include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
-#include "chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h"
+#include "chromeos/ash/components/cryptohome/constants.h"
 #include "chromeos/ash/components/osauth/public/auth_session_storage.h"
 
 namespace ash {
@@ -24,22 +22,9 @@
 void InSessionAuthTokenProviderImpl::ExchangeForToken(
     std::unique_ptr<UserContext> user_context,
     OnAuthTokenGenerated callback) {
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    AuthProofToken token =
-        AuthSessionStorage::Get()->Store(std::move(user_context));
-    // TODO(b/238606050): Determine token expiration.
-    std::move(callback).Run(token, quick_unlock::AuthToken::kTokenExpiration);
-  } else {
-    auto* quick_unlock_storage =
-        quick_unlock::QuickUnlockFactory::GetForAccountId(
-            user_context->GetAccountId());
-    quick_unlock_storage->MarkStrongAuth();
-    quick_unlock_storage->CreateAuthToken(*user_context);
-    auto token = quick_unlock_storage->GetAuthToken()->GetUnguessableToken();
-    DCHECK(token.has_value());
-    std::move(callback).Run(token.value().ToString(),
-                            quick_unlock::AuthToken::kTokenExpiration);
-  }
+  AuthProofToken token =
+      AuthSessionStorage::Get()->Store(std::move(user_context));
+  std::move(callback).Run(token, cryptohome::kAuthsessionInitialLifetime);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ui/cocoa/history_menu_bridge.h b/chrome/browser/ui/cocoa/history_menu_bridge.h
index db4f1c0..55645f64 100644
--- a/chrome/browser/ui/cocoa/history_menu_bridge.h
+++ b/chrome/browser/ui/cocoa/history_menu_bridge.h
@@ -271,7 +271,7 @@
 
   HistoryMenuCocoaController* __strong controller_;
 
-  raw_ptr<Profile, LeakedDanglingUntriaged> profile_;                   // weak
+  raw_ptr<Profile> profile_;                                            // weak
   raw_ptr<history::HistoryService> history_service_ = nullptr;          // weak
   raw_ptr<sessions::TabRestoreService> tab_restore_service_ = nullptr;  // weak
   base::FilePath profile_dir_;  // Remembered after OnProfileWillBeDestroyed().
diff --git a/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm b/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
index 90bceef..1a87fb7 100644
--- a/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
+++ b/chrome/browser/ui/cocoa/history_menu_bridge_unittest.mm
@@ -580,9 +580,9 @@
   std::unique_ptr<TestingProfile> profile = profile_builder.Build();
 
   auto bridge = std::make_unique<HistoryMenuBridge>(profile.get());
-  profile.reset();
   // Should not crash.
   bridge.reset();
+  profile.reset();
 }
 
 // Does a full setup and tear down of the bridge.
@@ -597,6 +597,7 @@
 
   auto bridge = std::make_unique<HistoryMenuBridge>(profile.get());
   bridge.reset();
+  profile.reset();
 }
 
 // Initializes the menu, then destroys the Profile but keeps the
diff --git a/chrome/browser/ui/hats/DEPS b/chrome/browser/ui/hats/DEPS
index f207e6f..aa788a9 100644
--- a/chrome/browser/ui/hats/DEPS
+++ b/chrome/browser/ui/hats/DEPS
@@ -6,5 +6,5 @@
 
   "trust_safety_sentiment_service_browsertest\.cc": [
     "+chrome/browser/ui/views/page_info",
-  ]
+  ],
 }
diff --git a/chrome/browser/ui/hats/hats_service.cc b/chrome/browser/ui/hats/hats_service.cc
index b15f911..5964190 100644
--- a/chrome/browser/ui/hats/hats_service.cc
+++ b/chrome/browser/ui/hats/hats_service.cc
@@ -4,596 +4,12 @@
 
 #include "chrome/browser/ui/hats/hats_service.h"
 
-#include <memory>
-#include <utility>
-
-#include "base/containers/contains.h"
-#include "base/feature_list.h"
-#include "base/json/values_util.h"
-#include "base/metrics/field_trial_params.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/rand_util.h"
-#include "base/ranges/algorithm.h"
-#include "base/task/sequenced_task_runner.h"
-#include "base/values.h"
-#include "base/version.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
-#include "chrome/browser/prefs/incognito_mode_prefs.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profiles_state.h"
-#include "chrome/browser/sessions/exit_type_service.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/common/chrome_features.h"
-#include "chrome/common/pref_names.h"
-#include "components/metrics_services_manager/metrics_services_manager.h"
-#include "components/policy/core/common/policy_pref_names.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/scoped_user_pref_update.h"
-#include "components/version_info/version_info.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/web_contents.h"
-#include "net/base/network_change_notifier.h"
-
-constexpr char kHatsShouldShowSurveyReasonHistogram[] =
-    "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReason";
-
-namespace {
-
-// TODO(crbug.com/1160661): When the minimum time between any survey, and the
-// minimum time between a specific survey, are the same, the logic supporting
-// the latter check is superfluous.
-constexpr base::TimeDelta kMinimumTimeBetweenSurveyStarts = base::Days(180);
-
-constexpr base::TimeDelta kMinimumTimeBetweenAnySurveyStarts = base::Days(180);
-
-constexpr base::TimeDelta kMinimumTimeBetweenSurveyChecks = base::Days(1);
-
-constexpr base::TimeDelta kMinimumProfileAge = base::Days(30);
-
-// Preferences Data Model
-// The kHatsSurveyMetadata pref points to a dictionary.
-// The valid keys and value types for this dictionary are as follows:
-// [trigger].last_major_version        ---> Integer
-// [trigger].last_survey_started_time  ---> Time
-// [trigger].is_survey_full            ---> Bool
-// [trigger].last_survey_check_time    ---> Time
-// any_last_survey_started_time        ---> Time
-
-std::string GetMajorVersionPath(const std::string& trigger) {
-  return trigger + ".last_major_version";
-}
-
-std::string GetLastSurveyStartedTime(const std::string& trigger) {
-  return trigger + ".last_survey_started_time";
-}
-
-std::string GetIsSurveyFull(const std::string& trigger) {
-  return trigger + ".is_survey_full";
-}
-
-std::string GetLastSurveyCheckTime(const std::string& trigger) {
-  return trigger + ".last_survey_check_time";
-}
-
-constexpr char kAnyLastSurveyStartedTimePath[] = "any_last_survey_started_time";
-
-}  // namespace
-
 HatsService::SurveyMetadata::SurveyMetadata() = default;
 
 HatsService::SurveyMetadata::~SurveyMetadata() = default;
 
-HatsService::DelayedSurveyTask::DelayedSurveyTask(
-    HatsService* hats_service,
-    const std::string& trigger,
-    content::WebContents* web_contents,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data,
-    bool require_same_origin)
-    : hats_service_(hats_service),
-      trigger_(trigger),
-      product_specific_bits_data_(product_specific_bits_data),
-      product_specific_string_data_(product_specific_string_data),
-      require_same_origin_(require_same_origin) {
-  Observe(web_contents);
-}
-
-HatsService::DelayedSurveyTask::~DelayedSurveyTask() = default;
-
-base::WeakPtr<HatsService::DelayedSurveyTask>
-HatsService::DelayedSurveyTask::GetWeakPtr() {
-  return weak_ptr_factory_.GetWeakPtr();
-}
-
-void HatsService::DelayedSurveyTask::Launch() {
-  hats_service_->LaunchSurveyForWebContents(trigger_, web_contents(),
-                                            product_specific_bits_data_,
-                                            product_specific_string_data_);
-  hats_service_->RemoveTask(*this);
-}
-
-void HatsService::DelayedSurveyTask::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
-  if (!require_same_origin_ || !navigation_handle ||
-      !navigation_handle->IsInPrimaryMainFrame() ||
-      navigation_handle->IsSameDocument() ||
-      (navigation_handle->HasCommitted() &&
-       navigation_handle->IsSameOrigin())) {
-    return;
-  }
-
-  hats_service_->RemoveTask(*this);
-}
-
-void HatsService::DelayedSurveyTask::WebContentsDestroyed() {
-  hats_service_->RemoveTask(*this);
-}
-
 HatsService::HatsService(Profile* profile) : profile_(profile) {
   hats::GetActiveSurveyConfigs(survey_configs_by_triggers_);
 }
 
 HatsService::~HatsService() = default;
-
-// static
-void HatsService::RegisterProfilePrefs(
-    user_prefs::PrefRegistrySyncable* registry) {
-  registry->RegisterDictionaryPref(
-      prefs::kHatsSurveyMetadata,
-      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-}
-
-void HatsService::LaunchSurvey(
-    const std::string& trigger,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  if (!ShouldShowSurvey(trigger)) {
-    std::move(failure_callback).Run();
-    return;
-  }
-
-  LaunchSurveyForBrowser(
-      chrome::FindLastActiveWithProfile(profile_), trigger,
-      std::move(success_callback), std::move(failure_callback),
-      product_specific_bits_data, product_specific_string_data);
-}
-
-bool HatsService::LaunchDelayedSurvey(
-    const std::string& trigger,
-    int timeout_ms,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  return base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
-      FROM_HERE,
-      base::BindOnce(&HatsService::LaunchSurvey, weak_ptr_factory_.GetWeakPtr(),
-                     trigger, base::DoNothing(), base::DoNothing(),
-                     product_specific_bits_data, product_specific_string_data),
-      base::Milliseconds(timeout_ms));
-}
-
-bool HatsService::LaunchDelayedSurveyForWebContents(
-    const std::string& trigger,
-    content::WebContents* web_contents,
-    int timeout_ms,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data,
-    bool require_same_origin) {
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (!web_contents) {
-    return false;
-  }
-  auto result = pending_tasks_.emplace(
-      this, trigger, web_contents, product_specific_bits_data,
-      product_specific_string_data, require_same_origin);
-  if (!result.second) {
-    return false;
-  }
-  auto success =
-      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
-          FROM_HERE,
-          base::BindOnce(
-              &HatsService::DelayedSurveyTask::Launch,
-              const_cast<HatsService::DelayedSurveyTask&>(*(result.first))
-                  .GetWeakPtr()),
-          base::Milliseconds(timeout_ms));
-  if (!success) {
-    pending_tasks_.erase(result.first);
-  }
-  return success;
-}
-
-void HatsService::RecordSurveyAsShown(std::string trigger_id) {
-  // Record the trigger associated with the trigger_id. This is recorded instead
-  // of the trigger ID itself, as the ID is specific to individual survey
-  // versions. There should be a cooldown before a user is prompted to take a
-  // survey from the same trigger, regardless of whether the survey was updated.
-  auto trigger_survey_config =
-      base::ranges::find(survey_configs_by_triggers_, trigger_id,
-                         [](const SurveyConfigs::value_type& pair) {
-                           return pair.second.trigger_id;
-                         });
-
-  DCHECK(trigger_survey_config != survey_configs_by_triggers_.end());
-  std::string trigger = trigger_survey_config->first;
-
-  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                            ShouldShowSurveyReasons::kYes);
-
-  ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
-  base::Value::Dict& pref_data = update.Get();
-  pref_data.SetByDottedPath(
-      GetMajorVersionPath(trigger),
-      static_cast<int>(version_info::GetVersion().components()[0]));
-  pref_data.SetByDottedPath(GetLastSurveyStartedTime(trigger),
-                            base::TimeToValue(base::Time::Now()));
-  pref_data.SetByDottedPath(kAnyLastSurveyStartedTimePath,
-                            base::TimeToValue(base::Time::Now()));
-}
-
-void HatsService::HatsNextDialogClosed() {
-  hats_next_dialog_exists_ = false;
-}
-
-void HatsService::SetSurveyMetadataForTesting(
-    const HatsService::SurveyMetadata& metadata) {
-  const std::string& trigger = kHatsSurveyTriggerSettings;
-  ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
-  base::Value::Dict& pref_data = update.Get();
-  if (!metadata.last_major_version.has_value() &&
-      !metadata.last_survey_started_time.has_value() &&
-      !metadata.is_survey_full.has_value() &&
-      !metadata.last_survey_check_time.has_value()) {
-    pref_data.RemoveByDottedPath(trigger);
-  }
-
-  if (metadata.last_major_version.has_value()) {
-    pref_data.SetByDottedPath(GetMajorVersionPath(trigger),
-                              *metadata.last_major_version);
-  } else {
-    pref_data.RemoveByDottedPath(GetMajorVersionPath(trigger));
-  }
-
-  if (metadata.last_survey_started_time.has_value()) {
-    pref_data.SetByDottedPath(
-        GetLastSurveyStartedTime(trigger),
-        base::TimeToValue(*metadata.last_survey_started_time));
-  } else {
-    pref_data.RemoveByDottedPath(GetLastSurveyStartedTime(trigger));
-  }
-
-  if (metadata.any_last_survey_started_time.has_value()) {
-    pref_data.SetByDottedPath(
-        kAnyLastSurveyStartedTimePath,
-        base::TimeToValue(*metadata.any_last_survey_started_time));
-  } else {
-    pref_data.RemoveByDottedPath(kAnyLastSurveyStartedTimePath);
-  }
-
-  if (metadata.is_survey_full.has_value()) {
-    pref_data.SetByDottedPath(GetIsSurveyFull(trigger),
-                              *metadata.is_survey_full);
-  } else {
-    pref_data.RemoveByDottedPath(GetIsSurveyFull(trigger));
-  }
-
-  if (metadata.last_survey_check_time.has_value()) {
-    pref_data.SetByDottedPath(
-        GetLastSurveyCheckTime(trigger),
-        base::TimeToValue(*metadata.last_survey_check_time));
-  } else {
-    pref_data.RemoveByDottedPath(GetLastSurveyCheckTime(trigger));
-  }
-}
-
-void HatsService::GetSurveyMetadataForTesting(
-    HatsService::SurveyMetadata* metadata) const {
-  const std::string& trigger = kHatsSurveyTriggerSettings;
-  ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
-  base::Value::Dict& pref_data = update.Get();
-
-  absl::optional<int> last_major_version =
-      pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
-  if (last_major_version.has_value()) {
-    metadata->last_major_version = last_major_version;
-  }
-
-  absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
-      pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
-  if (last_survey_started_time.has_value()) {
-    metadata->last_survey_started_time = last_survey_started_time;
-  }
-
-  absl::optional<base::Time> any_last_survey_started_time = base::ValueToTime(
-      pref_data.FindByDottedPath(kAnyLastSurveyStartedTimePath));
-  if (any_last_survey_started_time.has_value()) {
-    metadata->any_last_survey_started_time = any_last_survey_started_time;
-  }
-
-  absl::optional<bool> is_survey_full =
-      pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
-  if (is_survey_full.has_value()) {
-    metadata->is_survey_full = is_survey_full;
-  }
-
-  absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
-      pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
-  if (last_survey_check_time.has_value()) {
-    metadata->last_survey_check_time = last_survey_check_time;
-  }
-}
-
-void HatsService::RemoveTask(const DelayedSurveyTask& task) {
-  pending_tasks_.erase(task);
-}
-
-bool HatsService::HasPendingTasks() {
-  return !pending_tasks_.empty();
-}
-
-void HatsService::LaunchSurveyForWebContents(
-    const std::string& trigger,
-    content::WebContents* web_contents,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  if (ShouldShowSurvey(trigger) && web_contents &&
-      web_contents->GetVisibility() == content::Visibility::VISIBLE) {
-    LaunchSurveyForBrowser(chrome::FindBrowserWithTab(web_contents), trigger,
-                           base::DoNothing(), base::DoNothing(),
-                           product_specific_bits_data,
-                           product_specific_string_data);
-  }
-}
-
-void HatsService::LaunchSurveyForBrowser(
-    Browser* browser,
-    const std::string& trigger,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  if (!browser ||
-      (!browser->is_type_normal() && !browser->is_type_devtools()) ||
-      !profiles::IsRegularOrGuestSession(browser)) {
-    // Never show HaTS bubble for Incognito mode.
-    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                              ShouldShowSurveyReasons::kNoNotRegularBrowser);
-    std::move(failure_callback).Run();
-    return;
-  }
-  if (IncognitoModePrefs::GetAvailability(profile_->GetPrefs()) ==
-      policy::IncognitoModeAvailability::kDisabled) {
-    // Incognito mode needs to be enabled to create an off-the-record profile
-    // for HaTS dialog.
-    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                              ShouldShowSurveyReasons::kNoIncognitoDisabled);
-    std::move(failure_callback).Run();
-    return;
-  }
-  // Checking survey's status could be costly due to a network request, so
-  // we check it at the last.
-  CheckSurveyStatusAndMaybeShow(browser, trigger, std::move(success_callback),
-                                std::move(failure_callback),
-                                product_specific_bits_data,
-                                product_specific_string_data);
-}
-
-bool HatsService::CanShowSurvey(const std::string& trigger) const {
-  // Do not show if a survey dialog already exists.
-  if (hats_next_dialog_exists_) {
-    UMA_HISTOGRAM_ENUMERATION(
-        kHatsShouldShowSurveyReasonHistogram,
-        ShouldShowSurveyReasons::kNoSurveyAlreadyInProgress);
-    return false;
-  }
-
-  // Survey should not be loaded if the corresponding survey config is
-  // unavailable.
-  const auto config_iterator = survey_configs_by_triggers_.find(trigger);
-  if (config_iterator == survey_configs_by_triggers_.end()) {
-    UMA_HISTOGRAM_ENUMERATION(
-        kHatsShouldShowSurveyReasonHistogram,
-        ShouldShowSurveyReasons::kNoTriggerStringMismatch);
-    return false;
-  }
-  const hats::SurveyConfig config = config_iterator->second;
-
-  // Always show the survey in demo mode. This check is duplicated in
-  // CanShowAnySurvey, but because of the semantics of that function, must be
-  // included here.
-  if (base::FeatureList::IsEnabled(
-          features::kHappinessTrackingSurveysForDesktopDemo)) {
-    return true;
-  }
-
-  if (!CanShowAnySurvey(config.user_prompted)) {
-    return false;
-  }
-
-  // Survey can not be loaded and shown if there is no network connection.
-  if (net::NetworkChangeNotifier::IsOffline()) {
-    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                              ShouldShowSurveyReasons::kNoOffline);
-    return false;
-  }
-
-  const base::Value::Dict& pref_data =
-      profile_->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
-  absl::optional<int> last_major_version =
-      pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
-  if (last_major_version.has_value() &&
-      static_cast<uint32_t>(*last_major_version) ==
-          version_info::GetVersion().components()[0]) {
-    UMA_HISTOGRAM_ENUMERATION(
-        kHatsShouldShowSurveyReasonHistogram,
-        ShouldShowSurveyReasons::kNoReceivedSurveyInCurrentMilestone);
-    return false;
-  }
-
-  if (!config.user_prompted) {
-    absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
-        pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
-    if (last_survey_started_time.has_value()) {
-      base::TimeDelta elapsed_time_since_last_start =
-          base::Time::Now() - *last_survey_started_time;
-      if (elapsed_time_since_last_start < kMinimumTimeBetweenSurveyStarts) {
-        UMA_HISTOGRAM_ENUMERATION(
-            kHatsShouldShowSurveyReasonHistogram,
-            ShouldShowSurveyReasons::kNoLastSurveyTooRecent);
-        return false;
-      }
-    }
-  }
-
-  // If an attempt to check with the HaTS servers whether a survey should be
-  // delivered was made too recently, another survey cannot be shown.
-  absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
-      pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
-  if (last_survey_check_time.has_value()) {
-    base::TimeDelta elapsed_time_since_last_check =
-        base::Time::Now() - *last_survey_check_time;
-    if (elapsed_time_since_last_check < kMinimumTimeBetweenSurveyChecks) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool HatsService::CanShowAnySurvey(bool user_prompted) const {
-  // HaTS requires metrics consent to run. This is also how HaTS can be disabled
-  // by policy.
-  if (!g_browser_process->GetMetricsServicesManager()
-           ->IsMetricsConsentGiven()) {
-    return false;
-  }
-
-  // HaTs can also be disabled by policy if metrics consent is given.
-  if (!profile_->GetPrefs()->GetBoolean(
-          policy::policy_prefs::kFeedbackSurveysEnabled)) {
-    return false;
-  }
-
-  // Surveys can always be shown in Demo mode.
-  if (base::FeatureList::IsEnabled(
-          features::kHappinessTrackingSurveysForDesktopDemo)) {
-    return true;
-  }
-
-  // Do not show surveys if Chrome's last exit was a crash. This avoids
-  // biasing survey results unnecessarily.
-  if (ExitTypeService::GetLastSessionExitType(profile_) == ExitType::kCrashed) {
-    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                              ShouldShowSurveyReasons::kNoLastSessionCrashed);
-    return false;
-  }
-
-  // Some surveys may be "user prompted", which means the user has already been
-  // asked in context if they would like to take a survey (in a less
-  // confrontational manner than the standard HaTS prompt). The bar for whether
-  // a user is eligible is thus lower for these types of surveys.
-  if (!user_prompted) {
-    const base::Value::Dict& pref_data =
-        profile_->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
-
-    // If the profile is too new, measured as the age of the profile directory,
-    // the user is ineligible.
-    base::Time now = base::Time::Now();
-    if ((now - profile_->GetCreationTime()) < kMinimumProfileAge) {
-      UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
-                                ShouldShowSurveyReasons::kNoProfileTooNew);
-      return false;
-    }
-
-    // If a user has received any HaTS survey too recently, they are also
-    // ineligible.
-    absl::optional<base::Time> last_any_started_time =
-        base::ValueToTime(pref_data.Find(kAnyLastSurveyStartedTimePath));
-    if (last_any_started_time.has_value()) {
-      base::TimeDelta elapsed_time_any_started = now - *last_any_started_time;
-      if (elapsed_time_any_started < kMinimumTimeBetweenAnySurveyStarts) {
-        UMA_HISTOGRAM_ENUMERATION(
-            kHatsShouldShowSurveyReasonHistogram,
-            ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent);
-        return false;
-      }
-    }
-  }
-
-  return true;
-}
-
-bool HatsService::ShouldShowSurvey(const std::string& trigger) const {
-  if (!CanShowSurvey(trigger)) {
-    return false;
-  }
-
-  auto probability = survey_configs_by_triggers_.at(trigger).probability;
-  bool should_show_survey = base::RandDouble() < probability;
-  if (!should_show_survey) {
-    UMA_HISTOGRAM_ENUMERATION(
-        kHatsShouldShowSurveyReasonHistogram,
-        ShouldShowSurveyReasons::kNoBelowProbabilityLimit);
-  }
-
-  return should_show_survey;
-}
-
-void HatsService::CheckSurveyStatusAndMaybeShow(
-    Browser* browser,
-    const std::string& trigger,
-    base::OnceClosure success_callback,
-    base::OnceClosure failure_callback,
-    const SurveyBitsData& product_specific_bits_data,
-    const SurveyStringData& product_specific_string_data) {
-  // Check the survey status in profile first.
-  // We record the survey's over capacity information in user profile to avoid
-  // duplicated checks since the survey won't change once it is full.
-  const base::Value::Dict& pref_data =
-      profile_->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
-  absl::optional<int> is_full =
-      pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
-  if (is_full.has_value() && is_full) {
-    std::move(failure_callback).Run();
-    return;
-  }
-
-  CHECK(survey_configs_by_triggers_.find(trigger) !=
-        survey_configs_by_triggers_.end());
-  auto survey_config = survey_configs_by_triggers_[trigger];
-
-  // Check that the |product_specific_bits_data| matches the fields for this
-  // trigger. If fields are set for a trigger, they must be provided.
-  CHECK_EQ(product_specific_bits_data.size(),
-           survey_config.product_specific_bits_data_fields.size());
-  for (auto field_value : product_specific_bits_data) {
-    CHECK(base::Contains(survey_config.product_specific_bits_data_fields,
-                         field_value.first));
-  }
-
-  // Check that the |product_specific_string_data| matches the fields for this
-  // trigger. If fields are set for a trigger, they must be provided.
-  CHECK_EQ(product_specific_string_data.size(),
-           survey_config.product_specific_string_data_fields.size());
-  for (auto field_value : product_specific_string_data) {
-    CHECK(base::Contains(survey_config.product_specific_string_data_fields,
-                         field_value.first));
-  }
-
-  // As soon as the HaTS Next dialog is created it will attempt to contact
-  // the HaTS servers to check for a survey.
-  ScopedDictPrefUpdate update(profile_->GetPrefs(), prefs::kHatsSurveyMetadata);
-  update->SetByDottedPath(GetLastSurveyCheckTime(trigger),
-                          base::TimeToValue(base::Time::Now()));
-
-  DCHECK(!hats_next_dialog_exists_);
-  browser->window()->ShowHatsDialog(
-      survey_configs_by_triggers_[trigger].trigger_id,
-      std::move(success_callback), std::move(failure_callback),
-      product_specific_bits_data, product_specific_string_data);
-  hats_next_dialog_exists_ = true;
-}
diff --git a/chrome/browser/ui/hats/hats_service.h b/chrome/browser/ui/hats/hats_service.h
index 5eba9b9d..381d2b2a 100644
--- a/chrome/browser/ui/hats/hats_service.h
+++ b/chrome/browser/ui/hats/hats_service.h
@@ -5,13 +5,11 @@
 #ifndef CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_
 #define CHROME_BROWSER_UI_HATS_HATS_SERVICE_H_
 
-#include <memory>
-#include <set>
 #include <string>
 
 #include "base/containers/flat_map.h"
-#include "base/feature_list.h"
 #include "base/functional/callback.h"
+#include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
@@ -19,6 +17,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/ui/hats/survey_config.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -26,17 +25,8 @@
 class WebContents;
 }
 
-namespace user_prefs {
-class PrefRegistrySyncable;
-}
-
-class Browser;
 class Profile;
 
-// The name of the histogram which records if a survey was shown, or if not, the
-// reason why not.
-extern const char kHatsShouldShowSurveyReasonHistogram[];
-
 // Key-value mapping type for survey's product specific bits data.
 typedef std::map<std::string, bool> SurveyBitsData;
 
@@ -62,200 +52,105 @@
     absl::optional<base::Time> any_last_survey_started_time;
   };
 
-  class DelayedSurveyTask : public content::WebContentsObserver {
-   public:
-    DelayedSurveyTask(HatsService* hats_service,
-                      const std::string& trigger,
-                      content::WebContents* web_contents,
-                      const SurveyBitsData& product_specific_bits_data,
-                      const SurveyStringData& product_specific_string_data,
-                      bool require_same_origin);
-
-    // Not copyable or movable
-    DelayedSurveyTask(const DelayedSurveyTask&) = delete;
-    DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;
-
-    ~DelayedSurveyTask() override;
-
-    // Asks |hats_service_| to launch the survey with id |trigger_| for tab
-    // |web_contents_|.
-    void Launch();
-
-    // content::WebContentsObserver
-    void DidFinishNavigation(
-        content::NavigationHandle* navigation_handle) override;
-    void WebContentsDestroyed() override;
-
-    // Returns a weak pointer to this object.
-    base::WeakPtr<DelayedSurveyTask> GetWeakPtr();
-
-    bool operator<(const HatsService::DelayedSurveyTask& other) const {
-      return trigger_ < other.trigger_ ? true
-                                       : web_contents() < other.web_contents();
-    }
-
-   private:
-    raw_ptr<HatsService> hats_service_;
-    std::string trigger_;
-    SurveyBitsData product_specific_bits_data_;
-    SurveyStringData product_specific_string_data_;
-    bool require_same_origin_;
-    base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
-  };
-
-  // These values are persisted to logs. Entries should not be renumbered and
-  // numeric values should never be reused.
-  enum class ShouldShowSurveyReasons {
-    kYes = 0,
-    kNoOffline = 1,
-    kNoLastSessionCrashed = 2,
-    kNoReceivedSurveyInCurrentMilestone = 3,
-    kNoProfileTooNew = 4,
-    kNoLastSurveyTooRecent = 5,
-    kNoBelowProbabilityLimit = 6,
-    kNoTriggerStringMismatch = 7,
-    kNoNotRegularBrowser = 8,
-    kNoIncognitoDisabled = 9,
-    kNoCookiesBlocked = 10,            // Unused.
-    kNoThirdPartyCookiesBlocked = 11,  // Unused.
-    kNoSurveyUnreachable = 12,
-    kNoSurveyOverCapacity = 13,
-    kNoSurveyAlreadyInProgress = 14,
-    kNoAnyLastSurveyTooRecent = 15,
-    kNoRejectedByHatsService = 16,
-    kMaxValue = kNoRejectedByHatsService,
-  };
-
   explicit HatsService(Profile* profile);
-
   HatsService(const HatsService&) = delete;
   HatsService& operator=(const HatsService&) = delete;
 
   ~HatsService() override;
 
-  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
-
   // Launches survey with identifier |trigger| if appropriate.
   // |success_callback| is called when the survey is shown to the user.
   // |failure_callback| is called if the survey does not launch for any reason.
   // |product_specific_bits_data| and |product_specific_string_data| must
   // contain key-value pairs where the keys match the field names set for the
-  // survey in hats_service.cc, and the values are those which will be
-  // associated with the survey response. Field's matches are CHECK enforced.
+  // survey in survey_config.cc, and the values are those which will be
+  // associated with the survey response.
   virtual void LaunchSurvey(
       const std::string& trigger,
       base::OnceClosure success_callback = base::DoNothing(),
       base::OnceClosure failure_callback = base::DoNothing(),
       const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {});
+      const SurveyStringData& product_specific_string_data = {}) = 0;
 
   // Launches survey (with id |trigger|) with a timeout |timeout_ms| if
-  // appropriate. Survey will be shown at the active window/tab by the
-  // time of launching. Rejects (and returns false) if the underlying task
-  // posting fails.
+  // appropriate.
+  // |product_specific_bits_data| and |product_specific_string_data| must
+  // contain key-value pairs where the keys match the field names set for the
+  // survey in survey_config.cc, and the values are those which will be
+  // associated with the survey response.
+  // |web_contents| specifies the `WebContents` where the survey should be
+  // displayed. Returns false if the underlying task posting fails.
+  virtual void LaunchSurveyForWebContents(
+      const std::string& trigger,
+      content::WebContents* web_contents,
+      const SurveyBitsData& product_specific_bits_data,
+      const SurveyStringData& product_specific_string_data,
+      base::OnceClosure success_callback = base::DoNothing(),
+      base::OnceClosure failure_callback = base::DoNothing()) = 0;
+
+  // Launches survey (with id |trigger|) with a timeout |timeout_ms| if
+  // appropriate.
+  // |product_specific_bits_data| and |product_specific_string_data| must
+  // contain key-value pairs where the keys match the field names set for the
+  // survey in survey_config.cc, and the values are those which will be
+  // associated with the survey response.
   virtual bool LaunchDelayedSurvey(
       const std::string& trigger,
       int timeout_ms,
       const SurveyBitsData& product_specific_bits_data = {},
-      const SurveyStringData& product_specific_string_data = {});
+      const SurveyStringData& product_specific_string_data = {}) = 0;
 
   // Launches survey (with id |trigger|) with a timeout |timeout_ms| for tab
   // |web_contents| if appropriate. |web_contents| required to be non-nullptr.
-  // Launch is cancelled if |web_contents| killed before end of timeout. Launch
-  // is also cancelled if |web_contents| not visible at the time of launch.
+  // Launch is cancelled if |web_contents| killed before end of timeout.
   // Rejects (and returns false) if there is already an identical delayed-task
   // (same |trigger| and same |web_contents|) waiting to be fulfilled. Also
-  // rejects if the underlying task posting fails. If |require_same_origin| is
-  // set, additionally requires that |web_contents| remain on the same origin.
+  // rejects if the underlying task posting fails.
+  // If |require_same_origin| is set, additionally requires that |web_contents|
+  // remain on the same origin.
+  // |success_callback| is called when the survey is shown to the user.
+  // |failure_callback| is called if the survey does not launch for any reason.
   virtual bool LaunchDelayedSurveyForWebContents(
       const std::string& trigger,
       content::WebContents* web_contents,
       int timeout_ms,
       const SurveyBitsData& product_specific_bits_data = {},
       const SurveyStringData& product_specific_string_data = {},
-      bool require_same_origin = false);
+      bool require_same_origin = false,
+      base::OnceClosure success_callback = base::DoNothing(),
+      base::OnceClosure failure_callback = base::DoNothing()) = 0;
 
-  // Updates the user preferences to record that the survey associated with
-  // |survey_id| was shown to the user. |trigger_id| is the HaTS next Trigger
-  // ID for the survey.
-  void RecordSurveyAsShown(std::string trigger_id);
-
-  // Indicates to the service that the HaTS Next dialog has been closed.
-  // Virtual to allow mocking in tests.
-  virtual void HatsNextDialogClosed();
-
-  void SetSurveyMetadataForTesting(const SurveyMetadata& metadata);
-  void GetSurveyMetadataForTesting(HatsService::SurveyMetadata* metadata) const;
-  bool HasPendingTasks();
+  // Whether the user is eligible for any survey (of the type |user_prompted|
+  // or not) to be shown. A return value of false is always a true-negative,
+  // and means the user is currently ineligible for all surveys. A return value
+  // of true should not be interpreted as a guarantee that requests to show a
+  // survey will succeed.
+  virtual bool CanShowAnySurvey(bool user_prompted) const = 0;
 
   // Whether the survey specified by |trigger| can be shown to the user. This
   // is a pre-check that calculates as many conditions as possible, but could
   // still return a false positive due to client-side rate limiting, a change
   // in network conditions, or intervening calls to this API.
-  bool CanShowSurvey(const std::string& trigger) const;
+  virtual bool CanShowSurvey(const std::string& trigger) const = 0;
 
-  // Whether the user is eligible for any survey (of the type |user_prompted|
-  // or not) to be shown. A return value of false is always a true-negative, and
-  // means the user is currently ineligible for all surveys. A return value of
-  // true should not be interpreted as a guarantee that requests to show a
-  // survey will succeed. Virtual to allow mocking in tests.
-  virtual bool CanShowAnySurvey(bool user_prompted) const;
+  // Updates the user preferences to record that the survey associated with
+  // |survey_id| was shown to the user. |trigger_id| is the HaTS next Trigger
+  // ID for the survey.
+  virtual void RecordSurveyAsShown(std::string trigger_id) = 0;
 
-  // Returns whether a HaTS Next dialog currently exists, regardless of whether
-  // it is being shown or not.
-  bool hats_next_dialog_exists_for_testing() {
-    return hats_next_dialog_exists_;
-  }
+ protected:
+  hats::SurveyConfigs survey_configs_by_triggers_;
+  using SurveyConfigs = base::flat_map<std::string, hats::SurveyConfig>;
+
+  Profile* profile() const { return profile_; }
 
  private:
   friend class DelayedSurveyTask;
   FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
 
-  using SurveyConfigs = base::flat_map<std::string, hats::SurveyConfig>;
-
-  void LaunchSurveyForWebContents(
-      const std::string& trigger,
-      content::WebContents* web_contents,
-      const SurveyBitsData& product_specific_bits_data,
-      const SurveyStringData& product_specific_string_data);
-
-  void LaunchSurveyForBrowser(
-      Browser* browser,
-      const std::string& trigger,
-      base::OnceClosure success_callback,
-      base::OnceClosure failure_callback,
-      const SurveyBitsData& product_specific_bits_data,
-      const SurveyStringData& product_specific_string_data);
-
-  // Returns true is the survey trigger specified should be shown.
-  bool ShouldShowSurvey(const std::string& trigger) const;
-
-  // Check whether the survey is reachable and under capacity and show it.
-  // |success_callback| is called when the survey is shown to the user.
-  // |failure_callback| is called if the survey does not launch for any reason.
-  // The matches of field names with the `SurveyConfig` are CHECK enforced.
-  void CheckSurveyStatusAndMaybeShow(
-      Browser* browser,
-      const std::string& trigger,
-      base::OnceClosure success_callback,
-      base::OnceClosure failure_callback,
-      const SurveyBitsData& product_specific_bits_data,
-      const SurveyStringData& product_specific_string_data);
-
-  // Remove |task| from the set of |pending_tasks_|.
-  void RemoveTask(const DelayedSurveyTask& task);
-
   // Profile associated with this service.
   const raw_ptr<Profile> profile_;
 
-  std::set<DelayedSurveyTask> pending_tasks_;
-
-  SurveyConfigs survey_configs_by_triggers_;
-
-  // Whether a HaTS Next dialog currently exists (regardless of whether it
-  // is being shown to the user).
-  bool hats_next_dialog_exists_ = false;
-
   base::WeakPtrFactory<HatsService> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/hats/hats_service_browsertest.cc b/chrome/browser/ui/hats/hats_service_browsertest.cc
index 8843038..12e84b4 100644
--- a/chrome/browser/ui/hats/hats_service_browsertest.cc
+++ b/chrome/browser/ui/hats/hats_service_browsertest.cc
@@ -17,7 +17,7 @@
 #include "chrome/browser/profiles/profile_impl.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
-#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_desktop.h"
 #include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_features.h"
@@ -80,9 +80,9 @@
 
   ~HatsServiceBrowserTestBase() override = default;
 
-  HatsService* GetHatsService() {
-    HatsService* service =
-        HatsServiceFactory::GetForProfile(browser()->profile(), true);
+  HatsServiceDesktop* GetHatsService() {
+    HatsServiceDesktop* service = static_cast<HatsServiceDesktop*>(
+        HatsServiceFactory::GetForProfile(browser()->profile(), true));
     return service;
   }
 
@@ -218,20 +218,21 @@
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, SameMajorVersionNoShow) {
   SetMetricsConsent(true);
   base::HistogramTester histogram_tester;
-  HatsService::SurveyMetadata metadata;
+  HatsServiceDesktop::SurveyMetadata metadata;
   metadata.last_major_version = version_info::GetVersion().components()[0];
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsService::ShouldShowSurveyReasons::kNoReceivedSurveyInCurrentMilestone,
+      HatsServiceDesktop::ShouldShowSurveyReasons::
+          kNoReceivedSurveyInCurrentMilestone,
       1);
   EXPECT_FALSE(HatsNextDialogCreated());
 }
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, DifferentMajorVersionShow) {
   SetMetricsConsent(true);
-  HatsService::SurveyMetadata metadata;
+  HatsServiceDesktop::SurveyMetadata metadata;
   metadata.last_major_version = 42;
   ASSERT_NE(42u, version_info::GetVersion().components()[0]);
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
@@ -243,13 +244,13 @@
                        SurveyStartedBeforeRequiredElapsedTimeNoShow) {
   SetMetricsConsent(true);
   base::HistogramTester histogram_tester;
-  HatsService::SurveyMetadata metadata;
+  HatsServiceDesktop::SurveyMetadata metadata;
   metadata.last_survey_started_time = base::Time::Now();
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsService::ShouldShowSurveyReasons::kNoLastSurveyTooRecent, 1);
+      HatsServiceDesktop::ShouldShowSurveyReasons::kNoLastSurveyTooRecent, 1);
   EXPECT_FALSE(HatsNextDialogCreated());
 }
 
@@ -257,14 +258,15 @@
                        SurveyStartedBeforeElapsedTimeBetweenAnySurveys) {
   SetMetricsConsent(true);
   base::HistogramTester histogram_tester;
-  HatsService::SurveyMetadata metadata;
+  HatsServiceDesktop::SurveyMetadata metadata;
   metadata.any_last_survey_started_time = base::Time::Now();
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
   EXPECT_FALSE(HatsNextDialogCreated());
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsService::ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent, 1);
+      HatsServiceDesktop::ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent,
+      1);
 }
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, ProfileTooYoungToShow) {
@@ -276,7 +278,7 @@
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsService::ShouldShowSurveyReasons::kNoProfileTooNew, 1);
+      HatsServiceDesktop::ShouldShowSurveyReasons::kNoProfileTooNew, 1);
   EXPECT_FALSE(HatsNextDialogCreated());
 }
 
@@ -305,7 +307,7 @@
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, CheckedWithinADayNoShow) {
   SetMetricsConsent(true);
-  HatsService::SurveyMetadata metadata;
+  HatsServiceDesktop::SurveyMetadata metadata;
   metadata.last_survey_check_time = base::Time::Now() - base::Hours(23);
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
@@ -314,7 +316,7 @@
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, CheckedAfterADayToShow) {
   SetMetricsConsent(true);
-  HatsService::SurveyMetadata metadata;
+  HatsServiceDesktop::SurveyMetadata metadata;
   metadata.last_survey_check_time = base::Time::Now() - base::Days(1);
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
@@ -323,7 +325,7 @@
 
 IN_PROC_BROWSER_TEST_F(HatsServiceProbabilityOne, SurveyAlreadyFullNoShow) {
   SetMetricsConsent(true);
-  HatsService::SurveyMetadata metadata;
+  HatsServiceDesktop::SurveyMetadata metadata;
   metadata.is_survey_full = true;
   GetHatsService()->SetSurveyMetadataForTesting(metadata);
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
@@ -514,7 +516,7 @@
 
   GetHatsService()->LaunchSurvey(kHatsSurveyTriggerSettings);
 
-  HatsService::SurveyMetadata metadata;
+  HatsServiceDesktop::SurveyMetadata metadata;
   GetHatsService()->GetSurveyMetadataForTesting(&metadata);
   EXPECT_TRUE(metadata.last_survey_check_time.has_value());
 }
diff --git a/chrome/browser/ui/hats/hats_service_desktop.cc b/chrome/browser/ui/hats/hats_service_desktop.cc
new file mode 100644
index 0000000..e56b76f
--- /dev/null
+++ b/chrome/browser/ui/hats/hats_service_desktop.cc
@@ -0,0 +1,637 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/hats/hats_service_desktop.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/json/values_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/rand_util.h"
+#include "base/ranges/algorithm.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/prefs/incognito_mode_prefs.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profiles_state.h"
+#include "chrome/browser/sessions/exit_type_service.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "components/metrics_services_manager/metrics_services_manager.h"
+#include "components/policy/core/common/policy_pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/version_info/version_info.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/network_change_notifier.h"
+
+constexpr char kHatsShouldShowSurveyReasonHistogram[] =
+    "Feedback.HappinessTrackingSurvey.ShouldShowSurveyReason";
+
+namespace {
+
+// TODO(crbug.com/1160661): When the minimum time between any survey, and the
+// minimum time between a specific survey, are the same, the logic supporting
+// the latter check is superfluous.
+constexpr base::TimeDelta kMinimumTimeBetweenSurveyStarts = base::Days(180);
+
+constexpr base::TimeDelta kMinimumTimeBetweenAnySurveyStarts = base::Days(180);
+
+constexpr base::TimeDelta kMinimumTimeBetweenSurveyChecks = base::Days(1);
+
+constexpr base::TimeDelta kMinimumProfileAge = base::Days(30);
+
+// Preferences Data Model
+// The kHatsSurveyMetadata pref points to a dictionary.
+// The valid keys and value types for this dictionary are as follows:
+// [trigger].last_major_version        ---> Integer
+// [trigger].last_survey_started_time  ---> Time
+// [trigger].is_survey_full            ---> Bool
+// [trigger].last_survey_check_time    ---> Time
+// any_last_survey_started_time        ---> Time
+
+std::string GetMajorVersionPath(const std::string& trigger) {
+  return trigger + ".last_major_version";
+}
+
+std::string GetLastSurveyStartedTime(const std::string& trigger) {
+  return trigger + ".last_survey_started_time";
+}
+
+std::string GetIsSurveyFull(const std::string& trigger) {
+  return trigger + ".is_survey_full";
+}
+
+std::string GetLastSurveyCheckTime(const std::string& trigger) {
+  return trigger + ".last_survey_check_time";
+}
+
+constexpr char kAnyLastSurveyStartedTimePath[] = "any_last_survey_started_time";
+}  // namespace
+
+HatsServiceDesktop::DelayedSurveyTask::DelayedSurveyTask(
+    HatsServiceDesktop* hats_service,
+    const std::string& trigger,
+    content::WebContents* web_contents,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data,
+    bool require_same_origin,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback)
+    : hats_service_(hats_service),
+      trigger_(trigger),
+      product_specific_bits_data_(product_specific_bits_data),
+      product_specific_string_data_(product_specific_string_data),
+      require_same_origin_(require_same_origin),
+      success_callback_(std::move(success_callback)),
+      failure_callback_(std::move(failure_callback)) {
+  Observe(web_contents);
+}
+
+HatsServiceDesktop::DelayedSurveyTask::~DelayedSurveyTask() = default;
+
+base::WeakPtr<HatsServiceDesktop::DelayedSurveyTask>
+HatsServiceDesktop::DelayedSurveyTask::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+void HatsServiceDesktop::DelayedSurveyTask::Launch() {
+  CHECK(web_contents());
+  if (web_contents() &&
+      web_contents()->GetVisibility() == content::Visibility::VISIBLE) {
+    hats_service_->LaunchSurveyForWebContents(trigger_, web_contents(),
+                                              product_specific_bits_data_,
+                                              product_specific_string_data_);
+    hats_service_->RemoveTask(*this);
+  }
+}
+
+void HatsServiceDesktop::DelayedSurveyTask::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (!require_same_origin_ || !navigation_handle ||
+      !navigation_handle->IsInPrimaryMainFrame() ||
+      navigation_handle->IsSameDocument() ||
+      (navigation_handle->HasCommitted() &&
+       navigation_handle->IsSameOrigin())) {
+    return;
+  }
+
+  if (!failure_callback_.is_null()) {
+    std::move(failure_callback_).Run();
+  }
+  hats_service_->RemoveTask(*this);
+}
+
+void HatsServiceDesktop::DelayedSurveyTask::WebContentsDestroyed() {
+  if (!failure_callback_.is_null()) {
+    std::move(failure_callback_).Run();
+  }
+  hats_service_->RemoveTask(*this);
+}
+
+HatsServiceDesktop::HatsServiceDesktop(Profile* profile)
+    : HatsService(profile) {}
+
+HatsServiceDesktop::~HatsServiceDesktop() = default;
+
+// static
+void HatsServiceDesktop::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterDictionaryPref(
+      prefs::kHatsSurveyMetadata,
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+}
+
+void HatsServiceDesktop::LaunchSurvey(
+    const std::string& trigger,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  if (!ShouldShowSurvey(trigger)) {
+    if (!failure_callback.is_null()) {
+      std::move(failure_callback).Run();
+    }
+    return;
+  }
+  LaunchSurveyForBrowser(
+      chrome::FindLastActiveWithProfile(profile()), trigger,
+      std::move(success_callback), std::move(failure_callback),
+      product_specific_bits_data, product_specific_string_data);
+}
+
+void HatsServiceDesktop::LaunchSurveyForWebContents(
+    const std::string& trigger,
+    content::WebContents* web_contents,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback) {
+  if (ShouldShowSurvey(trigger) && web_contents &&
+      web_contents->GetVisibility() == content::Visibility::VISIBLE) {
+    LaunchSurveyForBrowser(
+        chrome::FindBrowserWithTab(web_contents), trigger,
+        std::move(success_callback), std::move(failure_callback),
+        product_specific_bits_data, product_specific_string_data);
+  }
+}
+
+bool HatsServiceDesktop::LaunchDelayedSurvey(
+    const std::string& trigger,
+    int timeout_ms,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  return base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE,
+      base::BindOnce(&HatsServiceDesktop::LaunchSurvey,
+                     weak_ptr_factory_.GetWeakPtr(), trigger, base::DoNothing(),
+                     base::DoNothing(), product_specific_bits_data,
+                     product_specific_string_data),
+      base::Milliseconds(timeout_ms));
+}
+
+bool HatsServiceDesktop::LaunchDelayedSurveyForWebContents(
+    const std::string& trigger,
+    content::WebContents* web_contents,
+    int timeout_ms,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data,
+    bool require_same_origin,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  if (survey_configs_by_triggers_.find(trigger) ==
+      survey_configs_by_triggers_.end()) {
+    // Survey configuration is not available.
+    if (!failure_callback.is_null()) {
+      std::move(failure_callback).Run();
+    }
+    return false;
+  }
+  if (!web_contents) {
+    if (!failure_callback.is_null()) {
+      std::move(failure_callback).Run();
+    }
+    return false;
+  }
+  auto result = pending_tasks_.emplace(
+      this, trigger, web_contents, product_specific_bits_data,
+      product_specific_string_data, require_same_origin,
+      std::move(success_callback), std::move(failure_callback));
+  if (!result.second) {
+    return false;
+  }
+  auto success =
+      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+          FROM_HERE,
+          base::BindOnce(&HatsServiceDesktop::DelayedSurveyTask::Launch,
+                         const_cast<HatsServiceDesktop::DelayedSurveyTask&>(
+                             *(result.first))
+                             .GetWeakPtr()),
+          base::Milliseconds(timeout_ms));
+  if (!success) {
+    pending_tasks_.erase(result.first);
+  }
+  return success;
+}
+
+void HatsServiceDesktop::SetSurveyMetadataForTesting(
+    const HatsService::SurveyMetadata& metadata) {
+  const std::string& trigger = kHatsSurveyTriggerSettings;
+  ScopedDictPrefUpdate update(profile()->GetPrefs(),
+                              prefs::kHatsSurveyMetadata);
+  base::Value::Dict& pref_data = update.Get();
+  if (!metadata.last_major_version.has_value() &&
+      !metadata.last_survey_started_time.has_value() &&
+      !metadata.is_survey_full.has_value() &&
+      !metadata.last_survey_check_time.has_value()) {
+    pref_data.RemoveByDottedPath(trigger);
+  }
+
+  if (metadata.last_major_version.has_value()) {
+    pref_data.SetByDottedPath(GetMajorVersionPath(trigger),
+                              *metadata.last_major_version);
+  } else {
+    pref_data.RemoveByDottedPath(GetMajorVersionPath(trigger));
+  }
+
+  if (metadata.last_survey_started_time.has_value()) {
+    pref_data.SetByDottedPath(
+        GetLastSurveyStartedTime(trigger),
+        base::TimeToValue(*metadata.last_survey_started_time));
+  } else {
+    pref_data.RemoveByDottedPath(GetLastSurveyStartedTime(trigger));
+  }
+
+  if (metadata.any_last_survey_started_time.has_value()) {
+    pref_data.SetByDottedPath(
+        kAnyLastSurveyStartedTimePath,
+        base::TimeToValue(*metadata.any_last_survey_started_time));
+  } else {
+    pref_data.RemoveByDottedPath(kAnyLastSurveyStartedTimePath);
+  }
+
+  if (metadata.is_survey_full.has_value()) {
+    pref_data.SetByDottedPath(GetIsSurveyFull(trigger),
+                              *metadata.is_survey_full);
+  } else {
+    pref_data.RemoveByDottedPath(GetIsSurveyFull(trigger));
+  }
+
+  if (metadata.last_survey_check_time.has_value()) {
+    pref_data.SetByDottedPath(
+        GetLastSurveyCheckTime(trigger),
+        base::TimeToValue(*metadata.last_survey_check_time));
+  } else {
+    pref_data.RemoveByDottedPath(GetLastSurveyCheckTime(trigger));
+  }
+}
+
+void HatsServiceDesktop::GetSurveyMetadataForTesting(
+    HatsService::SurveyMetadata* metadata) const {
+  const std::string& trigger = kHatsSurveyTriggerSettings;
+  ScopedDictPrefUpdate update(profile()->GetPrefs(),
+                              prefs::kHatsSurveyMetadata);
+  base::Value::Dict& pref_data = update.Get();
+
+  absl::optional<int> last_major_version =
+      pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
+  if (last_major_version.has_value()) {
+    metadata->last_major_version = last_major_version;
+  }
+
+  absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
+      pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
+  if (last_survey_started_time.has_value()) {
+    metadata->last_survey_started_time = last_survey_started_time;
+  }
+
+  absl::optional<base::Time> any_last_survey_started_time = base::ValueToTime(
+      pref_data.FindByDottedPath(kAnyLastSurveyStartedTimePath));
+  if (any_last_survey_started_time.has_value()) {
+    metadata->any_last_survey_started_time = any_last_survey_started_time;
+  }
+
+  absl::optional<bool> is_survey_full =
+      pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
+  if (is_survey_full.has_value()) {
+    metadata->is_survey_full = is_survey_full;
+  }
+
+  absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
+      pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
+  if (last_survey_check_time.has_value()) {
+    metadata->last_survey_check_time = last_survey_check_time;
+  }
+}
+
+bool HatsServiceDesktop::HasPendingTasks() {
+  return !pending_tasks_.empty();
+}
+
+bool HatsServiceDesktop::CanShowSurvey(const std::string& trigger) const {
+  // Survey should not be loaded if the corresponding survey config is
+  // unavailable.
+  const auto config_iterator = survey_configs_by_triggers_.find(trigger);
+
+  if (config_iterator == survey_configs_by_triggers_.end()) {
+    UMA_HISTOGRAM_ENUMERATION(
+        kHatsShouldShowSurveyReasonHistogram,
+        ShouldShowSurveyReasons::kNoTriggerStringMismatch);
+    return false;
+  }
+  const hats::SurveyConfig config = config_iterator->second;
+
+  // Do not show if a survey dialog already exists.
+  if (hats_next_dialog_exists_) {
+    UMA_HISTOGRAM_ENUMERATION(
+        kHatsShouldShowSurveyReasonHistogram,
+        ShouldShowSurveyReasons::kNoSurveyAlreadyInProgress);
+    return false;
+  }
+
+  // Always show the survey in demo mode. This check is duplicated in
+  // CanShowAnySurvey, but because of the semantics of that function, must be
+  // included here.
+  if (base::FeatureList::IsEnabled(
+          features::kHappinessTrackingSurveysForDesktopDemo)) {
+    return true;
+  }
+
+  if (!CanShowAnySurvey(config.user_prompted)) {
+    return false;
+  }
+
+  // Survey can not be loaded and shown if there is no network connection.
+  if (net::NetworkChangeNotifier::IsOffline()) {
+    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                              ShouldShowSurveyReasons::kNoOffline);
+    return false;
+  }
+
+  const base::Value::Dict& pref_data =
+      profile()->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
+  absl::optional<int> last_major_version =
+      pref_data.FindIntByDottedPath(GetMajorVersionPath(trigger));
+  if (last_major_version.has_value() &&
+      static_cast<uint32_t>(*last_major_version) ==
+          version_info::GetVersion().components()[0]) {
+    UMA_HISTOGRAM_ENUMERATION(
+        kHatsShouldShowSurveyReasonHistogram,
+        ShouldShowSurveyReasons::kNoReceivedSurveyInCurrentMilestone);
+    return false;
+  }
+
+  if (!config.user_prompted) {
+    absl::optional<base::Time> last_survey_started_time = base::ValueToTime(
+        pref_data.FindByDottedPath(GetLastSurveyStartedTime(trigger)));
+    if (last_survey_started_time.has_value()) {
+      base::TimeDelta elapsed_time_since_last_start =
+          base::Time::Now() - *last_survey_started_time;
+      if (elapsed_time_since_last_start < kMinimumTimeBetweenSurveyStarts) {
+        UMA_HISTOGRAM_ENUMERATION(
+            kHatsShouldShowSurveyReasonHistogram,
+            ShouldShowSurveyReasons::kNoLastSurveyTooRecent);
+        return false;
+      }
+    }
+  }
+
+  // If an attempt to check with the HaTS servers whether a survey should be
+  // delivered was made too recently, another survey cannot be shown.
+  absl::optional<base::Time> last_survey_check_time = base::ValueToTime(
+      pref_data.FindByDottedPath(GetLastSurveyCheckTime(trigger)));
+  if (last_survey_check_time.has_value()) {
+    base::TimeDelta elapsed_time_since_last_check =
+        base::Time::Now() - *last_survey_check_time;
+    if (elapsed_time_since_last_check < kMinimumTimeBetweenSurveyChecks) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool HatsServiceDesktop::CanShowAnySurvey(bool user_prompted) const {
+  // HaTS requires metrics consent to run. This is also how HaTS can be
+  // disabled by policy.
+  if (!g_browser_process->GetMetricsServicesManager()
+           ->IsMetricsConsentGiven()) {
+    return false;
+  }
+
+  // HaTs can also be disabled by policy if metrics consent is given.
+  if (!profile()->GetPrefs()->GetBoolean(
+          policy::policy_prefs::kFeedbackSurveysEnabled)) {
+    return false;
+  }
+
+  // Surveys can always be shown in Demo mode.
+  if (base::FeatureList::IsEnabled(
+          features::kHappinessTrackingSurveysForDesktopDemo)) {
+    return true;
+  }
+
+  // Do not show surveys if Chrome's last exit was a crash. This avoids
+  // biasing survey results unnecessarily.
+  if (ExitTypeService::GetLastSessionExitType(profile()) ==
+      ExitType::kCrashed) {
+    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                              ShouldShowSurveyReasons::kNoLastSessionCrashed);
+    return false;
+  }
+
+  // Some surveys may be "user prompted", which means the user has already
+  // been asked in context if they would like to take a survey (in a less
+  // confrontational manner than the standard HaTS prompt). The bar for
+  // whether a user is eligible is thus lower for these types of surveys.
+  if (!user_prompted) {
+    const base::Value::Dict& pref_data =
+        profile()->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
+
+    // If the profile is too new, measured as the age of the profile
+    // directory, the user is ineligible.
+    base::Time now = base::Time::Now();
+    if ((now - profile()->GetCreationTime()) < kMinimumProfileAge) {
+      UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                                ShouldShowSurveyReasons::kNoProfileTooNew);
+      return false;
+    }
+
+    // If a user has received any HaTS survey too recently, they are also
+    // ineligible.
+    absl::optional<base::Time> last_any_started_time =
+        base::ValueToTime(pref_data.Find(kAnyLastSurveyStartedTimePath));
+    if (last_any_started_time.has_value()) {
+      base::TimeDelta elapsed_time_any_started = now - *last_any_started_time;
+      if (elapsed_time_any_started < kMinimumTimeBetweenAnySurveyStarts) {
+        UMA_HISTOGRAM_ENUMERATION(
+            kHatsShouldShowSurveyReasonHistogram,
+            ShouldShowSurveyReasons::kNoAnyLastSurveyTooRecent);
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+void HatsServiceDesktop::RecordSurveyAsShown(std::string trigger_id) {
+  // Record the trigger associated with the trigger_id. This is recorded
+  // instead of the trigger ID itself, as the ID is specific to individual
+  // survey versions. There should be a cooldown before a user is prompted to
+  // take a survey from the same trigger, regardless of whether the survey was
+  // updated.
+  auto trigger_survey_config =
+      base::ranges::find(survey_configs_by_triggers_, trigger_id,
+                         [](const SurveyConfigs::value_type& pair) {
+                           return pair.second.trigger_id;
+                         });
+
+  DCHECK(trigger_survey_config != survey_configs_by_triggers_.end());
+  std::string trigger = trigger_survey_config->first;
+
+  UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                            ShouldShowSurveyReasons::kYes);
+
+  ScopedDictPrefUpdate update(profile()->GetPrefs(),
+                              prefs::kHatsSurveyMetadata);
+  base::Value::Dict& pref_data = update.Get();
+  pref_data.SetByDottedPath(
+      GetMajorVersionPath(trigger),
+      static_cast<int>(version_info::GetVersion().components()[0]));
+  pref_data.SetByDottedPath(GetLastSurveyStartedTime(trigger),
+                            base::TimeToValue(base::Time::Now()));
+  pref_data.SetByDottedPath(kAnyLastSurveyStartedTimePath,
+                            base::TimeToValue(base::Time::Now()));
+}
+
+void HatsServiceDesktop::HatsNextDialogClosed() {
+  hats_next_dialog_exists_ = false;
+}
+
+void HatsServiceDesktop::RemoveTask(const DelayedSurveyTask& task) {
+  pending_tasks_.erase(task);
+}
+
+bool HatsServiceDesktop::ShouldShowSurvey(const std::string& trigger) const {
+  if (!CanShowSurvey(trigger)) {
+    return false;
+  }
+
+  auto probability = survey_configs_by_triggers_.at(trigger).probability;
+  bool should_show_survey = base::RandDouble() < probability;
+  if (!should_show_survey) {
+    UMA_HISTOGRAM_ENUMERATION(
+        kHatsShouldShowSurveyReasonHistogram,
+        ShouldShowSurveyReasons::kNoBelowProbabilityLimit);
+  }
+
+  return should_show_survey;
+}
+
+void HatsServiceDesktop::LaunchSurveyForBrowser(
+    Browser* browser,
+    const std::string& trigger,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  if (!browser ||
+      (!browser->is_type_normal() && !browser->is_type_devtools()) ||
+      !profiles::IsRegularOrGuestSession(browser)) {
+    // Never show HaTS bubble for Incognito mode.
+    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                              ShouldShowSurveyReasons::kNoNotRegularBrowser);
+    if (!failure_callback.is_null()) {
+      std::move(failure_callback).Run();
+    }
+    return;
+  }
+  if (IncognitoModePrefs::GetAvailability(profile()->GetPrefs()) ==
+      policy::IncognitoModeAvailability::kDisabled) {
+    // Incognito mode needs to be enabled to create an off-the-record profile
+    // for HaTS dialog.
+    UMA_HISTOGRAM_ENUMERATION(kHatsShouldShowSurveyReasonHistogram,
+                              ShouldShowSurveyReasons::kNoIncognitoDisabled);
+    if (!failure_callback.is_null()) {
+      std::move(failure_callback).Run();
+    }
+    return;
+  }
+  // Checking survey's status could be costly due to a network request, so
+  // we check it at the last.
+  CheckSurveyStatusAndMaybeShow(browser, trigger, std::move(success_callback),
+                                std::move(failure_callback),
+                                product_specific_bits_data,
+                                product_specific_string_data);
+}
+
+void HatsServiceDesktop::CheckSurveyStatusAndMaybeShow(
+    Browser* browser,
+    const std::string& trigger,
+    base::OnceClosure success_callback,
+    base::OnceClosure failure_callback,
+    const SurveyBitsData& product_specific_bits_data,
+    const SurveyStringData& product_specific_string_data) {
+  // Check the survey status in profile first.
+  // We record the survey's over capacity information in user profile to avoid
+  // duplicated checks since the survey won't change once it is full.
+  const base::Value::Dict& pref_data =
+      profile()->GetPrefs()->GetDict(prefs::kHatsSurveyMetadata);
+  absl::optional<int> is_full =
+      pref_data.FindBoolByDottedPath(GetIsSurveyFull(trigger));
+  if (is_full.has_value() && is_full) {
+    if (!failure_callback.is_null()) {
+      std::move(failure_callback).Run();
+    }
+    return;
+  }
+
+  CHECK(survey_configs_by_triggers_.find(trigger) !=
+        survey_configs_by_triggers_.end());
+  auto survey_config = survey_configs_by_triggers_[trigger];
+
+  // Check that the |product_specific_bits_data| matches the fields for this
+  // trigger. If fields are set for a trigger, they must be provided.
+  CHECK_EQ(product_specific_bits_data.size(),
+           survey_config.product_specific_bits_data_fields.size());
+  for (auto field_value : product_specific_bits_data) {
+    CHECK(base::Contains(survey_config.product_specific_bits_data_fields,
+                         field_value.first));
+  }
+
+  // Check that the |product_specific_string_data| matches the fields for this
+  // trigger. If fields are set for a trigger, they must be provided.
+  CHECK_EQ(product_specific_string_data.size(),
+           survey_config.product_specific_string_data_fields.size());
+  for (auto field_value : product_specific_string_data) {
+    CHECK(base::Contains(survey_config.product_specific_string_data_fields,
+                         field_value.first));
+  }
+
+  // As soon as the HaTS Next dialog is created it will attempt to contact
+  // the HaTS servers to check for a survey.
+  ScopedDictPrefUpdate update(profile()->GetPrefs(),
+                              prefs::kHatsSurveyMetadata);
+  update->SetByDottedPath(GetLastSurveyCheckTime(trigger),
+                          base::TimeToValue(base::Time::Now()));
+
+  DCHECK(!hats_next_dialog_exists_);
+  browser->window()->ShowHatsDialog(
+      survey_configs_by_triggers_[trigger].trigger_id,
+      std::move(success_callback), std::move(failure_callback),
+      product_specific_bits_data, product_specific_string_data);
+  hats_next_dialog_exists_ = true;
+}
diff --git a/chrome/browser/ui/hats/hats_service_desktop.h b/chrome/browser/ui/hats/hats_service_desktop.h
new file mode 100644
index 0000000..a8824b0
--- /dev/null
+++ b/chrome/browser/ui/hats/hats_service_desktop.h
@@ -0,0 +1,207 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
+#define CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
+
+#include <set>
+#include <string>
+
+#include "base/functional/callback.h"
+#include "base/functional/callback_forward.h"
+#include "base/functional/callback_helpers.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/hats/hats_service.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "content/public/browser/web_contents_observer.h"
+
+class Browser;
+
+// Key-value mapping type for survey's product specific bits data.
+typedef std::map<std::string, bool> SurveyBitsData;
+
+// Key-value mapping type for survey's product specific string data.
+typedef std::map<std::string, std::string> SurveyStringData;
+
+// The name of the histogram which records if a survey was shown, or if not, the
+// reason why not.
+extern const char kHatsShouldShowSurveyReasonHistogram[];
+
+// This class provides the client side logic for determining if a
+// survey should be shown for any trigger based on input from a finch
+// configuration. It is created on a per profile basis.
+class HatsServiceDesktop : public HatsService {
+ public:
+  class DelayedSurveyTask : public content::WebContentsObserver {
+   public:
+    DelayedSurveyTask(HatsServiceDesktop* hats_service,
+                      const std::string& trigger,
+                      content::WebContents* web_contents,
+                      const SurveyBitsData& product_specific_bits_data,
+                      const SurveyStringData& product_specific_string_data,
+                      bool require_same_origin,
+                      base::OnceClosure success_callback,
+                      base::OnceClosure failure_callback);
+
+    // Not copyable or movable
+    DelayedSurveyTask(const DelayedSurveyTask&) = delete;
+    DelayedSurveyTask& operator=(const DelayedSurveyTask&) = delete;
+
+    ~DelayedSurveyTask() override;
+
+    // Asks |hats_service_| to launch the survey with id |trigger_| for tab
+    // |web_contents_|.
+    void Launch();
+
+    // content::WebContentsObserver
+    void DidFinishNavigation(
+        content::NavigationHandle* navigation_handle) override;
+    void WebContentsDestroyed() override;
+
+    // Returns a weak pointer to this object.
+    virtual base::WeakPtr<DelayedSurveyTask> GetWeakPtr();
+
+    bool operator<(const HatsServiceDesktop::DelayedSurveyTask& other) const {
+      return trigger_ < other.trigger_ ? true
+                                       : web_contents() < other.web_contents();
+    }
+
+   private:
+    raw_ptr<HatsServiceDesktop> hats_service_;
+
+    std::string trigger_;
+    SurveyBitsData product_specific_bits_data_;
+    SurveyStringData product_specific_string_data_;
+    bool require_same_origin_;
+    base::OnceClosure success_callback_;
+    base::OnceClosure failure_callback_;
+    base::WeakPtrFactory<DelayedSurveyTask> weak_ptr_factory_{this};
+  };
+
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class ShouldShowSurveyReasons {
+    kYes = 0,
+    kNoOffline = 1,
+    kNoLastSessionCrashed = 2,
+    kNoReceivedSurveyInCurrentMilestone = 3,
+    kNoProfileTooNew = 4,
+    kNoLastSurveyTooRecent = 5,
+    kNoBelowProbabilityLimit = 6,
+    kNoTriggerStringMismatch = 7,
+    kNoNotRegularBrowser = 8,
+    kNoIncognitoDisabled = 9,
+    kNoCookiesBlocked = 10,            // Unused.
+    kNoThirdPartyCookiesBlocked = 11,  // Unused.
+    kNoSurveyUnreachable = 12,
+    kNoSurveyOverCapacity = 13,
+    kNoSurveyAlreadyInProgress = 14,
+    kNoAnyLastSurveyTooRecent = 15,
+    kNoRejectedByHatsService = 16,
+    kMaxValue = kNoRejectedByHatsService,
+  };
+
+  explicit HatsServiceDesktop(Profile* profile);
+
+  HatsServiceDesktop(const HatsServiceDesktop&) = delete;
+  HatsServiceDesktop& operator=(const HatsServiceDesktop&) = delete;
+
+  ~HatsServiceDesktop() override;
+
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+  void LaunchSurvey(
+      const std::string& trigger,
+      base::OnceClosure success_callback = base::DoNothing(),
+      base::OnceClosure failure_callback = base::DoNothing(),
+      const SurveyBitsData& product_specific_bits_data = {},
+      const SurveyStringData& product_specific_string_data = {}) override;
+
+  void LaunchSurveyForWebContents(
+      const std::string& trigger,
+      content::WebContents* web_contents,
+      const SurveyBitsData& product_specific_bits_data,
+      const SurveyStringData& product_specific_string_data,
+      base::OnceClosure success_callback = base::DoNothing(),
+      base::OnceClosure failure_callback = base::DoNothing()) override;
+
+  bool LaunchDelayedSurvey(
+      const std::string& trigger,
+      int timeout_ms,
+      const SurveyBitsData& product_specific_bits_data = {},
+      const SurveyStringData& product_specific_string_data = {}) override;
+
+  bool LaunchDelayedSurveyForWebContents(
+      const std::string& trigger,
+      content::WebContents* web_contents,
+      int timeout_ms,
+      const SurveyBitsData& product_specific_bits_data = {},
+      const SurveyStringData& product_specific_string_data = {},
+      bool require_same_origin = false,
+      base::OnceClosure success_callback = base::DoNothing(),
+      base::OnceClosure failure_callback = base::DoNothing()) override;
+
+  void SetSurveyMetadataForTesting(const HatsService::SurveyMetadata& metadata);
+  void GetSurveyMetadataForTesting(HatsService::SurveyMetadata* metadata) const;
+
+  bool HasPendingTasks();
+
+  bool CanShowSurvey(const std::string& trigger) const override;
+
+  bool CanShowAnySurvey(bool user_prompted) const override;
+
+  void RecordSurveyAsShown(std::string trigger_id) override;
+
+  // Indicates to the service that the HaTS Next dialog has been closed.
+  virtual void HatsNextDialogClosed();
+
+  // Returns whether a HaTS Next dialog currently exists, regardless of whether
+  // it is being shown or not.
+  bool hats_next_dialog_exists_for_testing() {
+    return hats_next_dialog_exists_;
+  }
+
+ protected:
+ private:
+  FRIEND_TEST_ALL_PREFIXES(HatsServiceProbabilityOne, SingleHatsNextDialog);
+
+  // Remove |task| from the set of |pending_tasks_|.
+  void RemoveTask(const DelayedSurveyTask& task);
+
+  // Returns true is the survey trigger specified should be shown.
+  bool ShouldShowSurvey(const std::string& trigger) const;
+
+  void LaunchSurveyForBrowser(
+      Browser* browser,
+      const std::string& trigger,
+      base::OnceClosure success_callback,
+      base::OnceClosure failure_callback,
+      const SurveyBitsData& product_specific_bits_data,
+      const SurveyStringData& product_specific_string_data);
+
+  // Check whether the survey is reachable and under capacity and show it.
+  // |success_callback| is called when the survey is shown to the user.
+  // |failure_callback| is called if the survey does not launch for any reason.
+  // The matches of field names with the `SurveyConfig` are CHECK
+  // enforced.
+  void CheckSurveyStatusAndMaybeShow(
+      Browser* browser,
+      const std::string& trigger,
+      base::OnceClosure success_callback,
+      base::OnceClosure failure_callback,
+      const SurveyBitsData& product_specific_bits_data,
+      const SurveyStringData& product_specific_string_data);
+
+  std::set<DelayedSurveyTask> pending_tasks_;
+
+  // Whether a HaTS Next dialog currently exists (regardless of whether it
+  // is being shown to the user).
+  bool hats_next_dialog_exists_ = false;
+
+  base::WeakPtrFactory<HatsServiceDesktop> weak_ptr_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_UI_HATS_HATS_SERVICE_DESKTOP_H_
diff --git a/chrome/browser/ui/hats/hats_service_factory.cc b/chrome/browser/ui/hats/hats_service_factory.cc
index 6d3110d..59b45bc 100644
--- a/chrome/browser/ui/hats/hats_service_factory.cc
+++ b/chrome/browser/ui/hats/hats_service_factory.cc
@@ -7,12 +7,14 @@
 #include "base/no_destructor.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/ui/android/hats/hats_service_android.h"
 #include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_desktop.h"
 
 // static
 HatsService* HatsServiceFactory::GetForProfile(Profile* profile,
                                                bool create_if_necessary) {
-  return static_cast<HatsService*>(
+  return static_cast<HatsServiceAndroid*>(
       GetInstance()->GetServiceForBrowserContext(profile, create_if_necessary));
 }
 
@@ -32,7 +34,11 @@
 HatsServiceFactory::BuildServiceInstanceForBrowserContext(
     content::BrowserContext* context) const {
   Profile* profile = Profile::FromBrowserContext(context);
-  return std::make_unique<HatsService>(profile);
+#if BUILDFLAG(IS_ANDROID)
+  return std::make_unique<HatsServiceAndroid>(profile);
+#else
+  return std::make_unique<HatsServiceDesktop>(profile);
+#endif
 }
 
 HatsServiceFactory::~HatsServiceFactory() = default;
diff --git a/chrome/browser/ui/hats/hats_service_factory.h b/chrome/browser/ui/hats/hats_service_factory.h
index 1227b43..d2b67df 100644
--- a/chrome/browser/ui/hats/hats_service_factory.h
+++ b/chrome/browser/ui/hats/hats_service_factory.h
@@ -7,8 +7,8 @@
 
 #include "base/no_destructor.h"
 #include "chrome/browser/profiles/profile_keyed_service_factory.h"
+#include "chrome/browser/ui/hats/hats_service.h"
 
-class HatsService;
 class Profile;
 
 class HatsServiceFactory : public ProfileKeyedServiceFactory {
@@ -17,6 +17,7 @@
   HatsServiceFactory& operator=(const HatsServiceFactory&) = delete;
 
   static HatsService* GetForProfile(Profile* profile, bool create_if_necessary);
+
   static HatsServiceFactory* GetInstance();
 
  private:
diff --git a/chrome/browser/ui/hats/mock_hats_service.cc b/chrome/browser/ui/hats/mock_hats_service.cc
index d0d09992..16e7f95c 100644
--- a/chrome/browser/ui/hats/mock_hats_service.cc
+++ b/chrome/browser/ui/hats/mock_hats_service.cc
@@ -12,7 +12,8 @@
 
 using ::testing::NiceMock;
 
-MockHatsService::MockHatsService(Profile* profile) : HatsService(profile) {}
+MockHatsService::MockHatsService(Profile* profile)
+    : HatsServiceDesktop(profile) {}
 
 MockHatsService::~MockHatsService() = default;
 
diff --git a/chrome/browser/ui/hats/mock_hats_service.h b/chrome/browser/ui/hats/mock_hats_service.h
index a20db5b7..73caaf5 100644
--- a/chrome/browser/ui/hats/mock_hats_service.h
+++ b/chrome/browser/ui/hats/mock_hats_service.h
@@ -7,7 +7,8 @@
 
 #include <memory>
 
-#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_desktop.h"
+#include "content/public/browser/web_contents.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace content {
@@ -17,7 +18,7 @@
 class KeyedService;
 class Profile;
 
-class MockHatsService : public HatsService {
+class MockHatsService : public HatsServiceDesktop {
  public:
   explicit MockHatsService(Profile* profile);
   ~MockHatsService() override;
@@ -30,6 +31,15 @@
                (const SurveyBitsData&)survey_specific_bits_data,
                (const SurveyStringData&)survey_specific_string_data),
               (override));
+  MOCK_METHOD(void,
+              LaunchSurveyForWebContents,
+              (const std::string& trigger,
+               (content::WebContents*)web_contents,
+               (const SurveyBitsData&)survey_specific_bits_data,
+               (const SurveyStringData&)survey_specific_string_data,
+               base::OnceClosure success_callback,
+               base::OnceClosure failure_callback),
+              (override));
   MOCK_METHOD(bool,
               LaunchDelayedSurvey,
               (const std::string& trigger,
@@ -44,7 +54,9 @@
                int timeout_ms,
                (const SurveyBitsData&)survey_specific_bits_data,
                (const SurveyStringData&)survey_specific_string_data,
-               bool require_same_origin),
+               bool require_same_origin,
+               base::OnceClosure success_callback,
+               base::OnceClosure failure_callback),
               (override));
   MOCK_METHOD(void, HatsNextDialogClosed, (), (override));
   MOCK_METHOD(bool, CanShowAnySurvey, (bool user_prompted), (const override));
diff --git a/chrome/browser/ui/hats/survey_config.cc b/chrome/browser/ui/hats/survey_config.cc
index 23f63ed..1b91f555 100644
--- a/chrome/browser/ui/hats/survey_config.cc
+++ b/chrome/browser/ui/hats/survey_config.cc
@@ -3,15 +3,16 @@
 // found in the LICENSE file.
 
 #include "survey_config.h"
+
 #include "base/feature_list.h"
 #include "base/features.h"
+#include "components/permissions/features.h"
+#include "components/permissions/permission_hats_trigger_helper.h"
 
 #if !BUILDFLAG(IS_ANDROID)
 #include "chrome/common/chrome_features.h"
 #include "components/performance_manager/public/features.h"         // nogncheck
 #include "components/permissions/constants.h"                       // nogncheck
-#include "components/permissions/features.h"                        // nogncheck
-#include "components/permissions/permission_hats_trigger_helper.h"  // nogncheck
 #include "components/safe_browsing/core/common/features.h"          // nogncheck
 #include "components/safe_browsing/core/common/safebrowsing_constants.h"  // nogncheck
 #else
@@ -41,7 +42,6 @@
 // The permission prompt trigger permits configuring multiple triggers
 // simultaneously. Each trigger increments a counter at the end -->
 // "permission-prompt0", "permission-prompt1", ...
-constexpr char kHatsSurveyTriggerPermissionsPrompt[] = "permissions-prompt";
 constexpr char kHatsSurveyTriggerPrivacyGuide[] = "privacy-guide";
 constexpr char kHatsSurveyTriggerPrivacySandbox[] = "privacy-sandbox";
 constexpr char kHatsSurveyTriggerRedWarning[] = "red-warning";
@@ -116,6 +116,8 @@
 constexpr char kHatsNextSurveyTriggerIDTesting[] =
     "HLpeYy5Av0ugnJ3q1cK0XzzA8UHv";
 
+constexpr char kHatsSurveyTriggerPermissionsPrompt[] = "permissions-prompt";
+
 namespace {
 
 constexpr char kHatsSurveyProbability[] = "probability";
@@ -139,6 +141,29 @@
   default_survey.product_specific_string_data_fields = {"Test Field 3"};
   survey_configs.emplace_back(default_survey);
 
+  // Permissions surveys.
+  for (auto& trigger_id_pair : permissions::PermissionHatsTriggerHelper::
+           GetPermissionPromptTriggerIdPairs(
+               kHatsSurveyTriggerPermissionsPrompt)) {
+    // trigger_id_pair has structure <trigger_name, trigger_id>. trigger_name is
+    // a unique name used by the HaTS service integration, and trigger_id is an
+    // ID that specifies a survey in the Listnr backend.
+    survey_configs.emplace_back(
+        &permissions::features::kPermissionsPromptSurvey, trigger_id_pair.first,
+        trigger_id_pair.second,
+        std::vector<std::string>{
+            permissions::kPermissionsPromptSurveyHadGestureKey},
+        std::vector<std::string>{
+            permissions::kPermissionsPromptSurveyPromptDispositionKey,
+            permissions::kPermissionsPromptSurveyPromptDispositionReasonKey,
+            permissions::kPermissionsPromptSurveyActionKey,
+            permissions::kPermissionsPromptSurveyRequestTypeKey,
+            permissions::kPermissionsPromptSurveyReleaseChannelKey,
+            permissions::kPermissionsPromptSurveyDisplayTimeKey,
+            permissions::kPermissionPromptSurveyOneTimePromptsDecidedBucketKey,
+            permissions::kPermissionPromptSurveyUrlKey});
+  }
+
 #if !BUILDFLAG(IS_ANDROID)
   // Dev tools surveys.
   survey_configs.emplace_back(&features::kHaTSDesktopDevToolsIssuesCOEP,
@@ -433,29 +458,6 @@
       &features::kHappinessTrackingSurveysForDesktopWhatsNew,
       kHatsSurveyTriggerWhatsNew);
 
-  // Permissions surveys.
-  for (auto& trigger_id_pair : permissions::PermissionHatsTriggerHelper::
-           GetPermissionPromptTriggerIdPairs(
-               kHatsSurveyTriggerPermissionsPrompt)) {
-    // trigger_id_pair has structure <trigger_name, trigger_id>. trigger_name is
-    // a unique name used by the HaTS service integration, and trigger_id is an
-    // ID that specifies a survey in the Listnr backend.
-    survey_configs.emplace_back(
-        &permissions::features::kPermissionsPromptSurvey, trigger_id_pair.first,
-        trigger_id_pair.second,
-        std::vector<std::string>{
-            permissions::kPermissionsPromptSurveyHadGestureKey},
-        std::vector<std::string>{
-            permissions::kPermissionsPromptSurveyPromptDispositionKey,
-            permissions::kPermissionsPromptSurveyPromptDispositionReasonKey,
-            permissions::kPermissionsPromptSurveyActionKey,
-            permissions::kPermissionsPromptSurveyRequestTypeKey,
-            permissions::kPermissionsPromptSurveyReleaseChannelKey,
-            permissions::kPermissionsPromptSurveyDisplayTimeKey,
-            permissions::kPermissionPromptSurveyOneTimePromptsDecidedBucketKey,
-            permissions::kPermissionPromptSurveyUrlKey});
-  }
-
   // Performance Controls surveys.
   survey_configs.emplace_back(
       &performance_manager::features::kPerformanceControlsPerformanceSurvey,
diff --git a/chrome/browser/ui/hats/survey_config.h b/chrome/browser/ui/hats/survey_config.h
index 734e2bc7..e83e67c 100644
--- a/chrome/browser/ui/hats/survey_config.h
+++ b/chrome/browser/ui/hats/survey_config.h
@@ -30,7 +30,6 @@
 extern const char kHatsSurveyTriggerPerformanceControlsBatteryPerformance[];
 extern const char kHatsSurveyTriggerPerformanceControlsHighEfficiencyOptOut[];
 extern const char kHatsSurveyTriggerPerformanceControlsBatterySaverOptOut[];
-extern const char kHatsSurveyTriggerPermissionsPrompt[];
 extern const char kHatsSurveyTriggerPrivacyGuide[];
 extern const char kHatsSurveyTriggerPrivacySandbox[];
 extern const char kHatsSurveyTriggerRedWarning[];
@@ -74,6 +73,8 @@
 extern const char kHatsSurveyTriggerAndroidStartupSurvey[];
 #endif
 
+extern const char kHatsSurveyTriggerPermissionsPrompt[];
+
 extern const char kHatsSurveyTriggerTesting[];
 // The Trigger ID for a test HaTS Next survey which is available for testing
 // and demo purposes when the migration feature flag is enabled.
diff --git a/chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller.cc b/chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller.cc
new file mode 100644
index 0000000..dce39ca0
--- /dev/null
+++ b/chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller.cc
@@ -0,0 +1,80 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller.h"
+
+#include "components/password_manager/core/common/password_manager_pref_names.h"
+#include "components/password_manager/core/common/password_manager_ui.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+constexpr int kMaxNumberOfTimesBubbleIsShown = 3;
+
+// Returns whether to use Google Chrome branded strings.
+constexpr bool UsesPasswordManagerGoogleBranding() {
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+  return true;
+#else
+  return false;
+#endif
+}
+}  // namespace
+
+RelaunchChromeBubbleController::RelaunchChromeBubbleController(
+    base::WeakPtr<PasswordsModelDelegate> delegate,
+    PrefService* prefs)
+    : PasswordBubbleControllerBase(
+          std::move(delegate),
+          password_manager::metrics_util::AUTOMATIC_RELAUNCH_CHROME_BUBBLE),
+      prefs_(prefs) {}
+
+RelaunchChromeBubbleController::~RelaunchChromeBubbleController() {
+  OnBubbleClosing();
+}
+
+std::u16string RelaunchChromeBubbleController::GetTitle() const {
+  return l10n_util::GetStringUTF16(
+      UsesPasswordManagerGoogleBranding()
+          ? IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_BRANDED
+          : IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_NON_BRANDED);
+}
+
+std::u16string RelaunchChromeBubbleController::GetBody() const {
+  return l10n_util::GetStringUTF16(
+      UsesPasswordManagerGoogleBranding()
+          ? IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_BRANDED
+          : IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_NON_BRANDED);
+}
+
+std::u16string RelaunchChromeBubbleController::GetContinueButtonText() const {
+  return l10n_util::GetStringUTF16(
+      UsesPasswordManagerGoogleBranding()
+          ? IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_BRANDED
+          : IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_NON_BRANDED);
+}
+
+std::u16string RelaunchChromeBubbleController::GetNoThanksButtonText() const {
+  if (prefs_->GetInteger(
+          password_manager::prefs::kRelaunchChromeBubbleDismissedCounter) >=
+      kMaxNumberOfTimesBubbleIsShown) {
+    return l10n_util::GetStringUTF16(
+        IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_NEVER_BUTTON);
+  }
+
+  return l10n_util::GetStringUTF16(
+      IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_CANCEL_BUTTON);
+}
+
+void RelaunchChromeBubbleController::OnAccepted() {
+  delegate_->RelaunchChrome();
+}
+
+void RelaunchChromeBubbleController::OnCanceled() {
+  prefs_->SetInteger(
+      password_manager::prefs::kRelaunchChromeBubbleDismissedCounter,
+      prefs_->GetInteger(
+          password_manager::prefs::kRelaunchChromeBubbleDismissedCounter) +
+          1);
+}
diff --git a/chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller.h b/chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller.h
new file mode 100644
index 0000000..65ab6a3
--- /dev/null
+++ b/chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller.h
@@ -0,0 +1,38 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_PASSWORDS_BUBBLE_CONTROLLERS_RELAUNCH_CHROME_BUBBLE_CONTROLLER_H_
+#define CHROME_BROWSER_UI_PASSWORDS_BUBBLE_CONTROLLERS_RELAUNCH_CHROME_BUBBLE_CONTROLLER_H_
+
+#include "chrome/browser/ui/passwords/bubble_controllers/password_bubble_controller_base.h"
+#include "chrome/browser/ui/passwords/passwords_model_delegate.h"
+#include "components/prefs/pref_service.h"
+
+// This controller manages the relaunch Chrome bubble, which is shown while
+// password form is rendered and user has no keychain access.
+class RelaunchChromeBubbleController : public PasswordBubbleControllerBase {
+ public:
+  explicit RelaunchChromeBubbleController(
+      base::WeakPtr<PasswordsModelDelegate> delegate,
+      PrefService* prefs);
+  ~RelaunchChromeBubbleController() override;
+
+  std::u16string GetTitle() const override;
+  std::u16string GetBody() const;
+  std::u16string GetContinueButtonText() const;
+  std::u16string GetNoThanksButtonText() const;
+
+  // The user chose to relaunch the Chrome.
+  void OnAccepted();
+  // The user chose not to relaunch the Chrome.
+  void OnCanceled();
+
+ private:
+  // PasswordBubbleControllerBase:
+  void ReportInteractions() override {}
+
+  raw_ptr<PrefService> prefs_;
+};
+
+#endif  // CHROME_BROWSER_UI_PASSWORDS_BUBBLE_CONTROLLERS_RELAUNCH_CHROME_BUBBLE_CONTROLLER_H_
diff --git a/chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller_unittest.cc b/chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller_unittest.cc
new file mode 100644
index 0000000..c2151037
--- /dev/null
+++ b/chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller.h"
+
+#include "chrome/browser/ui/passwords/passwords_model_delegate_mock.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class RelaunchChromeBubbleControllerTest : public ::testing::Test {
+ public:
+  RelaunchChromeBubbleControllerTest() {
+    test_pref_service_ = std::make_unique<TestingPrefServiceSimple>();
+    test_pref_service_->registry()->RegisterIntegerPref(
+        password_manager::prefs::kRelaunchChromeBubbleDismissedCounter, 0);
+    mock_delegate_ =
+        std::make_unique<testing::NiceMock<PasswordsModelDelegateMock>>();
+  }
+  ~RelaunchChromeBubbleControllerTest() override = default;
+
+  PasswordsModelDelegateMock* delegate() { return mock_delegate_.get(); }
+  RelaunchChromeBubbleController* controller() { return controller_.get(); }
+
+  TestingPrefServiceSimple* test_pref_service() {
+    return test_pref_service_.get();
+  }
+
+  void CreateController() {
+    EXPECT_CALL(*delegate(), OnBubbleShown());
+    controller_ = std::make_unique<RelaunchChromeBubbleController>(
+        mock_delegate_->AsWeakPtr(), test_pref_service_.get());
+    EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(delegate()));
+  }
+
+ private:
+  std::unique_ptr<PasswordsModelDelegateMock> mock_delegate_;
+  std::unique_ptr<TestingPrefServiceSimple> test_pref_service_;
+  std::unique_ptr<RelaunchChromeBubbleController> controller_;
+};
+
+TEST_F(RelaunchChromeBubbleControllerTest, Content) {
+  CreateController();
+  EXPECT_NE(std::u16string(), controller()->GetTitle());
+  EXPECT_NE(std::u16string(), controller()->GetBody());
+  EXPECT_NE(std::u16string(), controller()->GetContinueButtonText());
+  EXPECT_NE(std::u16string(), controller()->GetNoThanksButtonText());
+}
+
+TEST_F(RelaunchChromeBubbleControllerTest, Cancel) {
+  CreateController();
+
+  controller()->OnCanceled();
+  EXPECT_EQ(test_pref_service()->GetInteger(
+                password_manager::prefs::kRelaunchChromeBubbleDismissedCounter),
+            1);
+}
+
+TEST_F(RelaunchChromeBubbleControllerTest, Restart) {
+  CreateController();
+
+  EXPECT_CALL(*delegate(), RelaunchChrome);
+  controller()->OnAccepted();
+}
diff --git a/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc b/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
index 501f554..e934c8a 100644
--- a/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
+++ b/chrome/browser/ui/passwords/manage_passwords_ui_controller.cc
@@ -19,6 +19,7 @@
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/feature_engagement/tracker_factory.h"
+#include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/password_manager/account_password_store_factory.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/password_manager/profile_password_store_factory.h"
@@ -880,6 +881,10 @@
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 }
 
+void ManagePasswordsUIController::RelaunchChrome() {
+  chrome::AttemptRestart();
+}
+
 void ManagePasswordsUIController::
     AuthenticateUserForAccountStoreOptInAndMovePassword() {
   DCHECK_EQ(GetState(),
diff --git a/chrome/browser/ui/passwords/manage_passwords_ui_controller.h b/chrome/browser/ui/passwords/manage_passwords_ui_controller.h
index 397c710..75f01c0 100644
--- a/chrome/browser/ui/passwords/manage_passwords_ui_controller.h
+++ b/chrome/browser/ui/passwords/manage_passwords_ui_controller.h
@@ -186,6 +186,7 @@
   void AuthenticateUserForAccountStoreOptInAfterSavingLocallyAndMovePassword()
       override;
   void MaybeShowIOSPasswordPromo() override;
+  void RelaunchChrome() override;
   // Skips user os level authentication during the life time of the returned
   // object. To be used in tests of flows that require user authentication.
   [[nodiscard]] std::unique_ptr<base::AutoReset<bool>>
diff --git a/chrome/browser/ui/passwords/passwords_model_delegate.h b/chrome/browser/ui/passwords/passwords_model_delegate.h
index 096c6dc..3e54d284 100644
--- a/chrome/browser/ui/passwords/passwords_model_delegate.h
+++ b/chrome/browser/ui/passwords/passwords_model_delegate.h
@@ -195,6 +195,9 @@
   // should show the user the Chrome for iOS promo.
   virtual void MaybeShowIOSPasswordPromo() = 0;
 
+  // Called from the Relaunch Chrome bubble to gracefully restart the Chrome.
+  virtual void RelaunchChrome() = 0;
+
  protected:
   virtual ~PasswordsModelDelegate() = default;
 };
diff --git a/chrome/browser/ui/passwords/passwords_model_delegate_mock.h b/chrome/browser/ui/passwords/passwords_model_delegate_mock.h
index d311a65..5f177d2 100644
--- a/chrome/browser/ui/passwords/passwords_model_delegate_mock.h
+++ b/chrome/browser/ui/passwords/passwords_model_delegate_mock.h
@@ -116,6 +116,7 @@
               (const std::u16string&),
               (override));
   MOCK_METHOD(void, MaybeShowIOSPasswordPromo, (), (override));
+  MOCK_METHOD(void, RelaunchChrome, (), (override));
 };
 
 #endif  // CHROME_BROWSER_UI_PASSWORDS_PASSWORDS_MODEL_DELEGATE_MOCK_H_
diff --git a/chrome/browser/ui/safety_hub/extensions_result.cc b/chrome/browser/ui/safety_hub/extensions_result.cc
index 4ce869a..b290d04 100644
--- a/chrome/browser/ui/safety_hub/extensions_result.cc
+++ b/chrome/browser/ui/safety_hub/extensions_result.cc
@@ -34,10 +34,11 @@
   bool warning_acked = false;
   extension_prefs->ReadPrefAsBoolean(
       extension.id(), kPrefAcknowledgeSafetyCheckWarning, &warning_acked);
+  bool is_extension = extension.is_extension() || extension.is_shared_module();
   // If the user has previously acknowledged the warning on this
   // extension and chosen to keep it, we will not show an additional
-  // Safety Hub warning.
-  if (warning_acked) {
+  // Safety Hub warning. We also will not show warnings on Chrome apps.
+  if (warning_acked || !is_extension) {
     return false;
   }
   absl::optional<extensions::CWSInfoService::CWSInfo> extension_info =
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.cc b/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.cc
index eb03502..9a152dc 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.cc
@@ -7,7 +7,6 @@
 #include "chrome/browser/ui/views/autofill/popup/popup_base_view.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_cell_utils.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_row_strategy.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_row_view.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_row_with_button_view.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
@@ -201,6 +200,29 @@
   return view;
 }
 
+// Creates the content view for regular address and credit card suggestions.
+// Content views for suggestions of other types and special suggestions are
+// created by corresponding `Create*PopupRowContentView()` methods.
+std::unique_ptr<PopupRowContentView> CreatePopupRowContentView(
+    const Suggestion& suggestion,
+    PopupType popup_type) {
+  auto view = std::make_unique<PopupRowContentView>();
+  std::unique_ptr<views::Label> main_text_label =
+      popup_cell_utils::CreateMainTextLabel(
+          suggestion.main_text,
+          GetMainTextStyleForPopupItemId(suggestion.popup_item_id));
+  popup_cell_utils::FormatLabel(*main_text_label, suggestion.main_text,
+                                popup_type);
+  popup_cell_utils::AddSuggestionContentToView(
+      suggestion, std::move(main_text_label),
+      popup_cell_utils::CreateMinorTextLabel(suggestion.minor_text),
+      /*description_label=*/nullptr,
+      popup_cell_utils::CreateAndTrackSubtextViews(*view, suggestion,
+                                                   popup_type),
+      *view);
+  return view;
+}
+
 // Creates the row for an Autocomplete entry with a delete button.
 std::unique_ptr<PopupRowWithButtonView> CreateAutocompleteRowWithDeleteButton(
     base::WeakPtr<AutofillPopupController> controller,
@@ -283,6 +305,7 @@
 
   const Suggestion& suggestion = controller->GetSuggestionAt(line_number);
   PopupItemId popup_item_id = suggestion.popup_item_id;
+  PopupType popup_type = controller->GetPopupType();
 
   if (popup_item_id == PopupItemId::kAutocompleteEntry &&
       base::FeatureList::IsEnabled(
@@ -291,7 +314,12 @@
         controller, a11y_selection_delegate, selection_delegate, line_number);
   }
 
-  std::unique_ptr<PopupRowStrategy> strategy;
+  if (IsFooterPopupItemId(popup_item_id)) {
+    return std::make_unique<PopupRowView>(
+        a11y_selection_delegate, selection_delegate, controller, line_number,
+        CreateFooterPopupRowContentView(suggestion));
+  }
+
   switch (popup_item_id) {
     // These `popup_item_id` should never be displayed in a `PopupRowView`.
     case PopupItemId::kSeparator:
@@ -317,26 +345,18 @@
               /*action_name=*/"compose_activated");
       auto row_view = std::make_unique<PopupRowView>(
           a11y_selection_delegate, selection_delegate, controller, line_number,
-          CreateComposePopupRowContentView(
-              suggestion, controller->GetPopupType(), show_new_badge));
+          CreateComposePopupRowContentView(suggestion, popup_type,
+                                           show_new_badge));
       row_view->set_new_badge_tracker(std::move(new_badge_tracker));
       return row_view;
     };
     default:
-      if (IsFooterPopupItemId(popup_item_id)) {
-        return std::make_unique<PopupRowView>(
-            a11y_selection_delegate, selection_delegate, controller,
-            line_number, CreateFooterPopupRowContentView(suggestion));
-      } else {
-        strategy =
-            std::make_unique<PopupSuggestionStrategy>(controller, line_number);
-      }
-      break;
+      return std::make_unique<PopupRowView>(
+          a11y_selection_delegate, selection_delegate, controller, line_number,
+          CreatePopupRowContentView(suggestion, popup_type));
   }
 
-  return std::make_unique<PopupRowView>(a11y_selection_delegate,
-                                        selection_delegate, controller,
-                                        line_number, strategy->CreateContent());
+  NOTREACHED_NORETURN();
 }
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils_unittest.cc b/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils_unittest.cc
index 80fa60de..a0291d7 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils_unittest.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_factory_utils_unittest.cc
@@ -6,7 +6,6 @@
 #include "base/check_op.h"
 #include "chrome/browser/ui/views/autofill/popup/mock_accessibility_selection_delegate.h"
 #include "chrome/browser/ui/views/autofill/popup/mock_selection_delegate.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_row_strategy.h"
 
 #include <memory>
 #include <utility>
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_strategy.cc b/chrome/browser/ui/views/autofill/popup/popup_row_strategy.cc
deleted file mode 100644
index 16c53d9..0000000
--- a/chrome/browser/ui/views/autofill/popup/popup_row_strategy.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/autofill/popup/popup_row_strategy.h"
-
-#include <memory>
-
-#include "base/feature_list.h"
-#include "chrome/app/vector_icons/vector_icons.h"
-#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_base_view.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_cell_utils.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
-#include "components/autofill/core/browser/metrics/autofill_metrics.h"
-#include "components/autofill/core/browser/ui/suggestion.h"
-#include "components/autofill/core/common/autofill_features.h"
-#include "components/strings/grit/components_strings.h"
-#include "components/user_education/views/new_badge_label.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/views/controls/button/image_button.h"
-#include "ui/views/controls/button/image_button_factory.h"
-#include "ui/views/controls/highlight_path_generator.h"
-#include "ui/views/controls/image_view.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/controls/menu/menu_config.h"
-#include "ui/views/controls/throbber.h"
-#include "ui/views/layout/box_layout_view.h"
-#include "ui/views/style/typography.h"
-#include "ui/views/vector_icons.h"
-#include "ui/views/view.h"
-
-namespace autofill {
-
-/**************************** PopupRowBaseStrategy ****************************/
-
-PopupRowBaseStrategy::PopupRowBaseStrategy(
-    base::WeakPtr<AutofillPopupController> controller,
-    int line_number)
-    : controller_(controller), line_number_(line_number) {
-  DCHECK(controller_);
-}
-
-PopupRowBaseStrategy::~PopupRowBaseStrategy() = default;
-
-int PopupRowBaseStrategy::GetLineNumber() const {
-  return line_number_;
-}
-
-/************************** PopupSuggestionStrategy ***************************/
-
-PopupSuggestionStrategy::PopupSuggestionStrategy(
-    base::WeakPtr<AutofillPopupController> controller,
-    int line_number)
-    : PopupRowBaseStrategy(std::move(controller), line_number) {}
-
-PopupSuggestionStrategy::~PopupSuggestionStrategy() = default;
-
-std::unique_ptr<PopupRowContentView> PopupSuggestionStrategy::CreateContent() {
-  if (!GetController()) {
-    return nullptr;
-  }
-
-  auto view = std::make_unique<PopupRowContentView>();
-  AddContentLabelsAndCallbacks(*view);
-  return view;
-}
-
-void PopupSuggestionStrategy::AddContentLabelsAndCallbacks(
-    PopupRowContentView& view) {
-  // Add the actual views.
-  const Suggestion& kSuggestion =
-      GetController()->GetSuggestionAt(GetLineNumber());
-  std::unique_ptr<views::Label> main_text_label =
-      popup_cell_utils::CreateMainTextLabel(
-          kSuggestion.main_text,
-          GetMainTextStyleForPopupItemId(kSuggestion.popup_item_id));
-  popup_cell_utils::FormatLabel(*main_text_label, kSuggestion.main_text,
-                                GetController()->GetPopupType());
-  popup_cell_utils::AddSuggestionContentToView(
-      kSuggestion, std::move(main_text_label),
-      popup_cell_utils::CreateMinorTextLabel(kSuggestion.minor_text),
-      /*description_label=*/nullptr,
-      popup_cell_utils::CreateAndTrackSubtextViews(
-          view, kSuggestion, GetController()->GetPopupType()),
-      view);
-}
-
-}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_strategy.h b/chrome/browser/ui/views/autofill/popup/popup_row_strategy.h
deleted file mode 100644
index 06ef7b8..0000000
--- a/chrome/browser/ui/views/autofill/popup/popup_row_strategy.h
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_POPUP_ROW_STRATEGY_H_
-#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_POPUP_ROW_STRATEGY_H_
-
-#include <memory>
-
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
-#include "components/autofill/core/browser/ui/popup_types.h"
-#include "components/autofill/core/browser/ui/suggestion.h"
-
-namespace views {
-class View;
-}
-
-namespace autofill {
-
-class AutofillPopupController;
-
-// `PopupRowStrategy` is an interface for content providers of `PopupRowView`.
-// `PopupRowView` is the actual `views::View` that owns the content and control
-// surfaces. Different types of popup rows can be created by passing different
-// implementations of `PopupRowStrategy` to the constructor of `PopupRowView`,
-// e.g. for password rows, Autofill rows, footer rows, etc.
-class PopupRowStrategy {
- public:
-  PopupRowStrategy() = default;
-  PopupRowStrategy(const PopupRowStrategy&) = delete;
-  PopupRowStrategy& operator=(const PopupRowStrategy&) = delete;
-  virtual ~PopupRowStrategy() = default;
-
-  // Creates the `PopupRowContentView` that contains the content area
-  // of the popup row.
-  virtual std::unique_ptr<PopupRowContentView> CreateContent() = 0;
-
- private:
-  // Returns the line number of the popup row that this strategy is for.
-  virtual int GetLineNumber() const = 0;
-};
-
-// A base class of methods shared between most strategies. It should never be
-// instantiated directly.
-class PopupRowBaseStrategy : public PopupRowStrategy {
- public:
-  PopupRowBaseStrategy(const PopupRowBaseStrategy&) = delete;
-  PopupRowBaseStrategy& operator=(const PopupRowBaseStrategy&) = delete;
-
-  int GetLineNumber() const override;
-  base::WeakPtr<AutofillPopupController> GetController() { return controller_; }
-  base::WeakPtr<const AutofillPopupController> GetController() const {
-    return controller_;
-  }
-
- protected:
-  PopupRowBaseStrategy(base::WeakPtr<AutofillPopupController> controller,
-                       int line_number);
-  ~PopupRowBaseStrategy() override;
-
- private:
-  // The controller of the popup.
-  const base::WeakPtr<AutofillPopupController> controller_;
-  // The line number of the popup.
-  const int line_number_;
-};
-
-// A `PopupRowStrategy` that creates the content for autocomplete, address, and
-// credit card popups.
-class PopupSuggestionStrategy : public PopupRowBaseStrategy {
- public:
-  PopupSuggestionStrategy(base::WeakPtr<AutofillPopupController> controller,
-                          int line_number);
-  PopupSuggestionStrategy(const PopupSuggestionStrategy&) = delete;
-  PopupSuggestionStrategy& operator=(const PopupSuggestionStrategy&) = delete;
-  ~PopupSuggestionStrategy() override;
-
-  // PopupRowStrategy:
-  std::unique_ptr<PopupRowContentView> CreateContent() override;
-
- private:
-  // Adds content and labels for a suggestion. A helper method used by all
-  // suggestion types.
-  void AddContentLabelsAndCallbacks(PopupRowContentView& view);
-};
-
-}  // namespace autofill
-
-#endif  // CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_POPUP_ROW_STRATEGY_H_
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_strategy_unittest.cc b/chrome/browser/ui/views/autofill/popup/popup_row_strategy_unittest.cc
deleted file mode 100644
index a1bed7f..0000000
--- a/chrome/browser/ui/views/autofill/popup/popup_row_strategy_unittest.cc
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/autofill/popup/popup_row_strategy.h"
-
-#include <memory>
-#include <utility>
-#include <vector>
-
-#include "base/test/scoped_feature_list.h"
-#include "chrome/browser/autofill/mock_autofill_popup_controller.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
-#include "chrome/test/views/chrome_views_test_base.h"
-#include "components/autofill/core/browser/ui/popup_item_ids.h"
-#include "components/autofill/core/browser/ui/suggestion.h"
-#include "components/autofill/core/common/autofill_features.h"
-#include "content/public/common/input/native_web_keyboard_event.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/events/base_event_utils.h"
-#include "ui/events/test/event_generator.h"
-#include "ui/views/widget/widget_utils.h"
-
-using ::testing::IsNull;
-using ::testing::NotNull;
-using ::testing::Return;
-
-namespace autofill {
-
-namespace {
-
-enum class StrategyType {
-  kSuggestion,
-};
-
-struct RowStrategyTestdata {
-  // The popup item ids of the suggestions to be shown.
-  std::vector<PopupItemId> popup_item_ids;
-  // The index of the suggestion to be tested.
-  int line_number;
-  // The type of strategy to be tested.
-  StrategyType strategy_type;
-};
-
-const RowStrategyTestdata kTestcases[] = {
-    RowStrategyTestdata{
-        .popup_item_ids = {PopupItemId::kAddressEntry,
-                           PopupItemId::kAddressEntry, PopupItemId::kSeparator,
-                           PopupItemId::kAutofillOptions},
-        .line_number = 1,
-        .strategy_type = StrategyType::kSuggestion,
-    },
-    RowStrategyTestdata{
-        .popup_item_ids = {PopupItemId::kAutocompleteEntry,
-                           PopupItemId::kAutocompleteEntry,
-                           PopupItemId::kAutocompleteEntry},
-        .line_number = 1,
-        .strategy_type = StrategyType::kSuggestion,
-    }};
-
-}  // namespace
-
-// Test fixture for testing PopupRowStrategy. Note that most of the detailed
-// view testing is covered by pixel tests in `popup_view_views_browsertest.cc`.
-class PopupRowStrategyTest : public ChromeViewsTestBase {
- public:
-  // Sets suggestions in the mocked popup controller.
-  void SetSuggestions(const std::vector<PopupItemId>& popup_item_ids) {
-    std::vector<Suggestion> suggestions;
-    suggestions.reserve(popup_item_ids.size());
-    for (PopupItemId popup_item_id : popup_item_ids) {
-      // Create a suggestion with empty labels.
-      suggestions.emplace_back("Main text", "", Suggestion::Icon::kNoIcon,
-                               popup_item_id);
-    }
-    controller().set_suggestions(std::move(suggestions));
-  }
-
-  std::unique_ptr<PopupRowStrategy> CreateStrategy(StrategyType type,
-                                                   int line_number) {
-    switch (type) {
-      case StrategyType::kSuggestion:
-        return std::make_unique<PopupSuggestionStrategy>(
-            controller().GetWeakPtr(), line_number);
-    }
-  }
-
-  MockAutofillPopupController& controller() { return controller_; }
-
- private:
-  MockAutofillPopupController controller_;
-};
-
-class PopupRowStrategyParametrizedTest
-    : public PopupRowStrategyTest,
-      public ::testing::WithParamInterface<RowStrategyTestdata> {};
-
-TEST_P(PopupRowStrategyParametrizedTest, HasContentArea) {
-  const RowStrategyTestdata kTestdata = GetParam();
-
-  SetSuggestions(kTestdata.popup_item_ids);
-  std::unique_ptr<PopupRowStrategy> strategy =
-      CreateStrategy(kTestdata.strategy_type, kTestdata.line_number);
-
-  // Every suggestion has a content area.
-  EXPECT_THAT(strategy->CreateContent(), NotNull());
-}
-
-INSTANTIATE_TEST_SUITE_P(All,
-                         PopupRowStrategyParametrizedTest,
-                         ::testing::ValuesIn(kTestcases));
-
-class PopupSuggestionStrategyTest : public ChromeViewsTestBase {
- public:
-  void SetUp() override {
-    ChromeViewsTestBase::SetUp();
-    widget_ = CreateTestWidget();
-    generator_ = std::make_unique<ui::test::EventGenerator>(
-        GetRootWindow(widget_.get()));
-  }
-
-  void TearDown() override {
-    view_ = nullptr;
-    generator_.reset();
-    widget_.reset();
-    ChromeViewsTestBase::TearDown();
-  }
-
-  void ShowSuggestion(Suggestion suggestion) {
-    // Show the button.
-    controller().set_suggestions({std::move(suggestion)});
-    strategy_ = std::make_unique<PopupSuggestionStrategy>(
-        controller().GetWeakPtr(), /*line_number=*/0);
-    view_ = widget_->SetContentsView(strategy_->CreateContent());
-    widget_->Show();
-  }
-
-  void ShowAutocompleteSuggestion() {
-    ShowSuggestion(Suggestion(u"Some entry", PopupItemId::kAutocompleteEntry));
-  }
-
- protected:
-  MockAutofillPopupController& controller() { return controller_; }
-  ui::test::EventGenerator& generator() { return *generator_; }
-  PopupRowContentView& view() { return *view_; }
-  views::Widget& widget() { return *widget_; }
-
- private:
-  std::unique_ptr<views::Widget> widget_;
-  std::unique_ptr<ui::test::EventGenerator> generator_;
-  raw_ptr<PopupRowContentView> view_ = nullptr;
-  MockAutofillPopupController controller_;
-  std::unique_ptr<PopupSuggestionStrategy> strategy_;
-  // All current Autocomplete tests assume that the deletion button feature is
-  // enabled.
-  base::test::ScopedFeatureList feature_list{
-      features::kAutofillShowAutocompleteDeleteButton};
-};
-
-}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_view.cc b/chrome/browser/ui/views/autofill/popup/popup_row_view.cc
index 40cb9a4..b605f67 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_view.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_view.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 #include <optional>
-#include <string>
 #include <utility>
 
 #include "base/check_op.h"
@@ -19,7 +18,6 @@
 #include "chrome/browser/ui/views/autofill/popup/popup_cell_utils.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_row_strategy.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_row_with_button_view.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_view_views.h"
@@ -36,12 +34,11 @@
 #include "ui/events/event_utils.h"
 #include "ui/events/types/event_type.h"
 #include "ui/gfx/geometry/insets.h"
-#include "ui/gfx/geometry/insets_outsets_base.h"
 #include "ui/gfx/geometry/outsets_f.h"
+#include "ui/gfx/geometry/rect_f.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/layout/box_layout.h"
-#include "ui/views/metadata/type_conversion.h"
 #include "ui/views/view.h"
 #include "ui/views/view_class_properties.h"
 
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_view.h b/chrome/browser/ui/views/autofill/popup/popup_row_view.h
index 2af840c..d6b8f9b 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_view.h
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_view.h
@@ -8,7 +8,6 @@
 #include <memory>
 #include <optional>
 
-#include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
 #include "base/memory/weak_ptr.h"
@@ -19,7 +18,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/events/event_handler.h"
-#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_f.h"
 #include "ui/views/view.h"
 #include "ui/views/view_observer.h"
 
@@ -33,11 +32,12 @@
 
 class AutofillPopupController;
 class PopupRowContentView;
-class PopupRowStrategy;
 
-// `PopupRowView` represents a single selectable popup row. Different styles
-// of the row can be achieved by injecting the respective `PopupRowStrategy`
-// objects in the constructor.
+// `PopupRowView` represents a single selectable popup row. It contains logic
+// common to all row types (like selection callbacks or a11y) but it is not
+// responsible for the view layout. It expects a `PopupRowContentView` instead.
+// It also supports the expanding control depending on whether the suggestion
+// has children or not (see `Suggestion::children`).
 class PopupRowView : public views::View, public views::ViewObserver {
   METADATA_HEADER(PopupRowView, views::View)
  public:
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_view_unittest.cc b/chrome/browser/ui/views/autofill/popup/popup_row_view_unittest.cc
index 9714f79..a4d3a0e 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_view_unittest.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_view_unittest.cc
@@ -15,13 +15,16 @@
 #include "chrome/browser/ui/views/autofill/popup/mock_accessibility_selection_delegate.h"
 #include "chrome/browser/ui/views/autofill/popup/mock_selection_delegate.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_row_strategy.h"
+#include "chrome/browser/ui/views/autofill/popup/popup_row_factory_utils.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
-#include "chrome/browser/ui/views/autofill/popup/test_popup_row_strategy.h"
+#include "chrome/test/base/testing_profile.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
 #include "components/autofill/core/browser/ui/suggestion.h"
+#include "content/public/browser/web_contents.h"
 #include "content/public/common/input/native_web_keyboard_event.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
@@ -62,6 +65,11 @@
     widget_ = CreateTestWidget();
     generator_ = std::make_unique<ui::test::EventGenerator>(
         GetRootWindow(widget_.get()));
+    web_contents_ =
+        content::WebContentsTester::CreateTestWebContents(&profile_, nullptr);
+
+    ON_CALL(mock_controller_, GetWebContents())
+        .WillByDefault(Return(web_contents_.get()));
   }
 
   void ShowView(int line_number, bool has_control) {
@@ -76,19 +84,18 @@
 
   void ShowView(int line_number, std::vector<Suggestion> suggestions) {
     mock_controller_.set_suggestions(suggestions);
-    ShowView(std::make_unique<TestPopupRowStrategy>(line_number));
+    ShowView(line_number);
   }
 
   void ShowView(int line_number, std::vector<PopupItemId> suggestions) {
     mock_controller_.set_suggestions(suggestions);
-    ShowView(std::make_unique<TestPopupRowStrategy>(line_number));
+    ShowView(line_number);
   }
 
-  void ShowView(std::unique_ptr<TestPopupRowStrategy> strategy) {
-    row_view_ = widget_->SetContentsView(std::make_unique<PopupRowView>(
-        mock_a11y_selection_delegate_, mock_selection_delegate_,
-        mock_controller_.GetWeakPtr(), strategy->GetLineNumber(),
-        strategy->CreateContent()));
+  void ShowView(int line_number) {
+    row_view_ = widget_->SetContentsView(CreatePopupRowView(
+        mock_controller_.GetWeakPtr(), mock_a11y_selection_delegate_,
+        mock_selection_delegate_, line_number));
     ON_CALL(mock_selection_delegate_, SetSelectedCell)
         .WillByDefault([this](absl::optional<CellIndex> cell,
                               PopupCellSelectionSource) {
@@ -137,6 +144,9 @@
   PopupRowView& row_view() { return *row_view_; }
 
  private:
+  content::RenderViewHostTestEnabler render_view_host_test_enabler_;
+  TestingProfile profile_;
+  std::unique_ptr<content::WebContents> web_contents_;
   std::unique_ptr<views::Widget> widget_;
   std::unique_ptr<ui::test::EventGenerator> generator_;
   NiceMock<MockAccessibilitySelectionDelegate> mock_a11y_selection_delegate_;
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_with_button_view_unittest.cc b/chrome/browser/ui/views/autofill/popup/popup_row_with_button_view_unittest.cc
index 7ddade99..a637dc9 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_with_button_view_unittest.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_with_button_view_unittest.cc
@@ -16,7 +16,6 @@
 #include "chrome/browser/ui/views/autofill/popup/mock_accessibility_selection_delegate.h"
 #include "chrome/browser/ui/views/autofill/popup/mock_selection_delegate.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
-#include "chrome/browser/ui/views/autofill/popup/test_popup_row_strategy.h"
 #include "chrome/test/views/chrome_views_test_base.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
diff --git a/chrome/browser/ui/views/autofill/popup/test_popup_row_strategy.cc b/chrome/browser/ui/views/autofill/popup/test_popup_row_strategy.cc
deleted file mode 100644
index 3cc34f8b..0000000
--- a/chrome/browser/ui/views/autofill/popup/test_popup_row_strategy.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/autofill/popup/test_popup_row_strategy.h"
-
-#include <memory>
-
-#include "components/autofill/core/common/aliases.h"
-#include "ui/accessibility/ax_enums.mojom.h"
-#include "ui/accessibility/ax_node_data.h"
-#include "ui/views/controls/label.h"
-
-namespace autofill {
-
-TestPopupRowStrategy::TestPopupRowStrategy(int line_number)
-    : line_number_(line_number) {}
-
-TestPopupRowStrategy::~TestPopupRowStrategy() = default;
-
-std::unique_ptr<PopupRowContentView> TestPopupRowStrategy::CreateContent() {
-  auto cell = std::make_unique<PopupRowContentView>();
-  cell->SetUseDefaultFillLayout(true);
-  cell->AddChildView(std::make_unique<views::Label>(u"Test content"));
-  return cell;
-}
-
-int TestPopupRowStrategy::GetLineNumber() const {
-  return line_number_;
-}
-
-}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/popup/test_popup_row_strategy.h b/chrome/browser/ui/views/autofill/popup/test_popup_row_strategy.h
deleted file mode 100644
index 1677f18..0000000
--- a/chrome/browser/ui/views/autofill/popup/test_popup_row_strategy.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_TEST_POPUP_ROW_STRATEGY_H_
-#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_TEST_POPUP_ROW_STRATEGY_H_
-
-#include <memory>
-
-#include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
-#include "chrome/browser/ui/views/autofill/popup/popup_row_strategy.h"
-
-namespace autofill {
-
-// A `PopupRowStrategy` used solely in tests.
-class TestPopupRowStrategy : public PopupRowStrategy {
- public:
-  explicit TestPopupRowStrategy(int line_number);
-  ~TestPopupRowStrategy() override;
-
-  std::unique_ptr<PopupRowContentView> CreateContent() override;
-
-  int GetLineNumber() const override;
-
- private:
-  const int line_number_;
-};
-
-}  // namespace autofill
-
-#endif  // CHROME_BROWSER_UI_VIEWS_AUTOFILL_POPUP_TEST_POPUP_ROW_STRATEGY_H_
diff --git a/chrome/browser/ui/views/hats/hats_browsertest.cc b/chrome/browser/ui/views/hats/hats_browsertest.cc
index 35f9eb8..e5431d9 100644
--- a/chrome/browser/ui/views/hats/hats_browsertest.cc
+++ b/chrome/browser/ui/views/hats/hats_browsertest.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/devtools/devtools_window_testing.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_desktop.h"
 #include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/hats/mock_hats_service.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
@@ -49,7 +49,7 @@
 
 // The locale expected by the test survey. This value is checked in
 // hats_next_mock.html for tests that expect a loaded response.
-const std::string kTestLocale = "lt";
+const char kTestLocale[] = "lt";
 
 }  // namespace
 
@@ -212,7 +212,7 @@
   // Because no loaded state was provided, only a rejection should be recorded.
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsService::ShouldShowSurveyReasons::kNoRejectedByHatsService, 1);
+      HatsServiceDesktop::ShouldShowSurveyReasons::kNoRejectedByHatsService, 1);
 }
 
 // Test that a survey which first reports as loaded, then reports closure, only
@@ -238,7 +238,7 @@
   // The only recorded sample should indicate that the survey was shown.
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsService::ShouldShowSurveyReasons::kYes, 1);
+      HatsServiceDesktop::ShouldShowSurveyReasons::kYes, 1);
 }
 
 // Test that if the survey does not indicate it is ready for display before the
@@ -259,7 +259,7 @@
   EXPECT_EQ(1, failure_count);
   histogram_tester.ExpectUniqueSample(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsService::ShouldShowSurveyReasons::kNoSurveyUnreachable, 1);
+      HatsServiceDesktop::ShouldShowSurveyReasons::kNoSurveyUnreachable, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, UnknownURLFragment) {
diff --git a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
index 59849e7b..8b1ebe3 100644
--- a/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
+++ b/chrome/browser/ui/views/hats/hats_next_web_dialog.cc
@@ -8,6 +8,7 @@
 #include "base/json/json_writer.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/devtools/devtools_window.h"
@@ -15,6 +16,7 @@
 #include "chrome/browser/profiles/profile_destroyer.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/hats/hats_service.h"
+#include "chrome/browser/ui/hats/hats_service_desktop.h"
 #include "chrome/browser/ui/hats/hats_service_factory.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/app_menu_button.h"
@@ -213,7 +215,7 @@
     // such as a survey still being in test mode, or an invalid survey ID.
     base::UmaHistogramEnumeration(
         kHatsShouldShowSurveyReasonHistogram,
-        HatsService::ShouldShowSurveyReasons::kNoRejectedByHatsService);
+        HatsServiceDesktop::ShouldShowSurveyReasons::kNoRejectedByHatsService);
     std::move(failure_callback_).Run();
   }
   CloseWidget();
@@ -284,12 +286,16 @@
 }
 
 HatsNextWebDialog::~HatsNextWebDialog() {
+#if IS_ANDROID
+  NOTIMPLEMENTED();  // This class is for desktop only. Enforce assumption.
+#endif
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (otr_profile_) {
     otr_profile_->RemoveObserver(this);
     ProfileDestroyer::DestroyOTRProfileWhenAppropriate(otr_profile_);
   }
-  auto* service = HatsServiceFactory::GetForProfile(browser_->profile(), false);
+  HatsServiceDesktop* service = static_cast<HatsServiceDesktop*>(
+      HatsServiceFactory::GetForProfile(browser_->profile(), false));
   DCHECK(service);
   service->HatsNextDialogClosed();
 
@@ -340,7 +346,7 @@
 void HatsNextWebDialog::LoadTimedOut() {
   base::UmaHistogramEnumeration(
       kHatsShouldShowSurveyReasonHistogram,
-      HatsService::ShouldShowSurveyReasons::kNoSurveyUnreachable);
+      HatsServiceDesktop::ShouldShowSurveyReasons::kNoSurveyUnreachable);
   CloseWidget();
   std::move(failure_callback_).Run();
 }
diff --git a/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc b/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc
index bead1bca..f287431 100644
--- a/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc
+++ b/chrome/browser/ui/views/permissions/embedded_permission_prompt_base_view.cc
@@ -99,7 +99,8 @@
 
 void EmbeddedPermissionPromptBaseView::UpdateAnchor(views::Widget* widget) {
   SetAnchorView(widget->GetContentsView());
-  set_parent_window(widget->GetNativeView());
+  set_parent_window(
+      platform_util::GetViewForWindow(browser_->window()->GetNativeWindow()));
   SetArrow(views::BubbleBorder::Arrow::FLOAT);
 }
 
diff --git a/chrome/browser/ui/webui/ash/settings/pages/multidevice/multidevice_handler.cc b/chrome/browser/ui/webui/ash/settings/pages/multidevice/multidevice_handler.cc
index deb4d6d..c0c9997 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/multidevice/multidevice_handler.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/multidevice/multidevice_handler.cc
@@ -762,15 +762,7 @@
 }
 
 bool MultideviceHandler::IsAuthTokenValid(const std::string& auth_token) {
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    return ash::AuthSessionStorage::Get()->IsValid(auth_token);
-  } else {
-    Profile* profile = Profile::FromWebUI(web_ui());
-    quick_unlock::QuickUnlockStorage* quick_unlock_storage =
-        quick_unlock::QuickUnlockFactory::GetForProfile(profile);
-    return quick_unlock_storage->GetAuthToken() &&
-           auth_token == quick_unlock_storage->GetAuthToken()->Identifier();
-  }
+  return ash::AuthSessionStorage::Get()->IsValid(auth_token);
 }
 
 multidevice_setup::MultiDeviceSetupClient::HostStatusWithDevice
diff --git a/chrome/browser/ui/webui/ash/settings/pages/people/fingerprint_handler.cc b/chrome/browser/ui/webui/ash/settings/pages/people/fingerprint_handler.cc
index 2fedc0856..5769c97 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/people/fingerprint_handler.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/people/fingerprint_handler.cc
@@ -310,19 +310,7 @@
 }
 
 bool FingerprintHandler::CheckAuthTokenValidity(const std::string& auth_token) {
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    return ash::AuthSessionStorage::Get()->IsValid(auth_token);
-  } else {
-    quick_unlock::QuickUnlockStorage* quick_unlock_storage =
-        quick_unlock::QuickUnlockFactory::GetForProfile(profile_);
-    if (!quick_unlock_storage->GetAuthToken()) {
-      return false;
-    }
-    if (auth_token != quick_unlock_storage->GetAuthToken()->Identifier()) {
-      return false;
-    }
-    return true;
-  }
+  return ash::AuthSessionStorage::Get()->IsValid(auth_token);
 }
 
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc
index 9d07fc7..45b419a 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler_unittest.cc
@@ -960,7 +960,7 @@
       {});
 
   EXPECT_CALL(*mock_hats_service(),
-              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _))
+              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _, _, _))
       .Times(1);
   const std::vector<std::string> module_ids = {"recipe_tasks", "cart"};
   handler_->OnModulesLoadedWithData(module_ids);
@@ -977,7 +977,7 @@
       {});
 
   EXPECT_CALL(*mock_hats_service(),
-              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _))
+              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _, _, _))
       .Times(0);
   const std::vector<std::string> module_ids = {"recipe_tasks"};
   handler_->OnModulesLoadedWithData(module_ids);
diff --git a/chrome/browser/ui/webui/settings/hats_handler_unittest.cc b/chrome/browser/ui/webui/settings/hats_handler_unittest.cc
index ca7cef7a..f9d5cd23 100644
--- a/chrome/browser/ui/webui/settings/hats_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/hats_handler_unittest.cc
@@ -110,7 +110,7 @@
   EXPECT_CALL(*mock_hats_service_,
               LaunchDelayedSurveyForWebContents(
                   kHatsSurveyTriggerSettingsPrivacy, web_contents(), 15000,
-                  expected_product_specific_data, _, true))
+                  expected_product_specific_data, _, true, _, _))
       .Times(2);
   base::Value::List args;
   args.Append(
@@ -128,7 +128,7 @@
   // Check that completing a privacy guide triggers a privacy guide hats.
   EXPECT_CALL(*mock_hats_service_, LaunchDelayedSurveyForWebContents(
                                        kHatsSurveyTriggerPrivacyGuide,
-                                       web_contents(), 15000, _, _, true))
+                                       web_contents(), 15000, _, _, true, _, _))
       .Times(1);
   base::Value::List args;
   args.Append(static_cast<int>(
@@ -159,7 +159,7 @@
   EXPECT_CALL(*mock_hats_service_,
               LaunchDelayedSurveyForWebContents(
                   kHatsSurveyTriggerSettingsSecurity, web_contents(), 15000, _,
-                  expected_product_specific_data, true))
+                  expected_product_specific_data, true, _, _))
       .Times(1);
 
   base::Value::List args;
@@ -196,7 +196,7 @@
   // Enable targeting for users who have not seen the Privacy Sandbox page and
   // ensure the handler does not attempt to launch the survey.
   EXPECT_CALL(*mock_hats_service_,
-              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _))
+              LaunchDelayedSurveyForWebContents(_, _, _, _, _, _, _, _))
       .Times(0);
 
   profile()->GetPrefs()->SetBoolean(prefs::kPrivacySandboxPageViewed, true);
@@ -221,7 +221,7 @@
   EXPECT_CALL(*mock_hats_service_,
               LaunchDelayedSurveyForWebContents(
                   kHatsSurveyTriggerPrivacySandbox, web_contents(), 10000,
-                  expected_product_specific_data, _, true));
+                  expected_product_specific_data, _, true, _, _));
   base::Value::List args;
   args.Append(static_cast<int>(
       HatsHandler::TrustSafetyInteraction::OPENED_PRIVACY_SANDBOX));
@@ -302,7 +302,7 @@
     EXPECT_CALL(*mock_hats_service_,
                 LaunchDelayedSurveyForWebContents(
                     survey, web_contents(), 20000,
-                    expected_product_specific_data, _, true));
+                    expected_product_specific_data, _, true, _, _));
     base::Value::List args;
     args.Append(static_cast<int>(interaction));
     handler()->HandleTrustSafetyInteractionOccurred(args);
diff --git a/chrome/browser/ui/webui/settings/safety_check_extensions_handler.cc b/chrome/browser/ui/webui/settings/safety_check_extensions_handler.cc
index d11cc3d4..19cc95df5 100644
--- a/chrome/browser/ui/webui/settings/safety_check_extensions_handler.cc
+++ b/chrome/browser/ui/webui/settings/safety_check_extensions_handler.cc
@@ -53,10 +53,11 @@
   bool warning_acked = false;
   extension_prefs->ReadPrefAsBoolean(
       extension.id(), kPrefAcknowledgeSafetyCheckWarning, &warning_acked);
+  bool is_extension = extension.is_extension() || extension.is_shared_module();
   // If the user has previously acknowledged the warning on this
   // extension and chosen to keep it, we will not show an additional
-  // safety hub warning.
-  if (warning_acked) {
+  // safety hub warning. We also will not show warnings on Chrome apps.
+  if (warning_acked || !is_extension) {
     return false;
   }
   absl::optional<extensions::CWSInfoService::CWSInfo> extension_info =
diff --git a/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc b/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
index 37a5250..8489f63 100644
--- a/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui_browsertest.cc
@@ -56,7 +56,7 @@
           browser()->profile(), base::BindRepeating(&BuildMockHatsService)));
   EXPECT_CALL(*mock_hats_service_,
               LaunchDelayedSurveyForWebContents(kHatsSurveyTriggerSettings, _,
-                                                _, _, _, _));
+                                                _, _, _, _, _, _));
   ASSERT_TRUE(NavigateToURL(browser(), GURL(chrome::kChromeUISettingsURL)));
   base::RunLoop().RunUntilIdle();
 }
diff --git a/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl.cc b/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl.cc
index 85314938..ad792e2 100644
--- a/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl.cc
+++ b/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl.cc
@@ -14,6 +14,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
+#include "base/uuid.h"
 #include "base/values.h"
 #include "chrome/browser/ash/account_manager/account_apps_availability.h"
 #include "chrome/browser/ash/account_manager/account_apps_availability_factory.h"
@@ -39,10 +40,14 @@
 #include "components/prefs/pref_service.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/user_manager/known_user.h"
+#include "components/user_manager/user.h"
+#include "components/user_manager/user_manager.h"
 #include "crypto/sha2.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/webui/web_ui_util.h"
@@ -80,7 +85,8 @@
   return account_device_id;
 }
 
-std::string GetInlineLoginFlowName(Profile* profile, const std::string* email) {
+std::string GetInlineLoginFlowName(Profile* profile,
+                                   const absl::optional<std::string>& email) {
   DCHECK(profile);
   if (!profile->IsChild()) {
     return kCrosAddAccountFlow;
@@ -134,6 +140,24 @@
       *email};
 }
 
+std::string GetDeviceId(user_manager::KnownUser& known_user,
+                        const AccountId& device_account_id,
+                        const absl::optional<std::string>& initial_email) {
+  if (!initial_email ||
+      !gaia::AreEmailsSame(*initial_email, device_account_id.GetUserEmail())) {
+    // Return a random GUID for account additions (`!initial_email`) and
+    // Secondary Account reauth.
+    return base::Uuid::GenerateRandomV4().AsLowercaseString();
+  }
+
+  std::string device_id = known_user.GetDeviceId(device_account_id);
+  if (device_id.empty()) {
+    // This should not happen but we need to handle this gracefully.
+    device_id = base::Uuid::GenerateRandomV4().AsLowercaseString();
+  }
+  return device_id;
+}
+
 class EduCoexistenceChildSigninHelper : public SigninHelper {
  public:
   EduCoexistenceChildSigninHelper(
@@ -263,9 +287,16 @@
       base::BindRepeating(
           &InlineLoginHandlerImpl::OpenGuestWindowAndCloseDialog,
           base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "getDeviceId", base::BindRepeating(&InlineLoginHandlerImpl::GetDeviceId,
+                                         base::Unretained(this)));
 }
 
 void InlineLoginHandlerImpl::SetExtraInitParams(base::Value::Dict& params) {
+  std::string* email = params.FindString("email");
+  if (email && !email->empty()) {
+    initial_email_ = *email;
+  }
   const GaiaUrls* const gaia_urls = GaiaUrls::GetInstance();
   params.Set("clientId", gaia_urls->oauth2_chrome_client_id());
 
@@ -277,7 +308,7 @@
   params.Set("platformVersion", version.value_or("0.0.0.0"));
   params.Set("constrained", "1");
   params.Set("flow", GetInlineLoginFlowName(Profile::FromWebUI(web_ui()),
-                                            params.FindString("email")));
+                                            initial_email_));
   params.Set("dontResizeNonEmbeddedPages", true);
   params.Set("enableGaiaActionButtons", true);
   params.Set("forceDarkMode",
@@ -489,4 +520,16 @@
   close_dialog_closure_.Run();
 }
 
+void InlineLoginHandlerImpl::GetDeviceId(const base::Value::List& args) {
+  CHECK_EQ(1u, args.size());
+  const std::string& callback_id = args[0].GetString();
+
+  user_manager::KnownUser known_user{g_browser_process->local_state()};
+  const AccountId& device_account_id =
+      user_manager::UserManager::Get()->GetPrimaryUser()->GetAccountId();
+  ResolveJavascriptCallback(
+      callback_id,
+      ::ash::GetDeviceId(known_user, device_account_id, initial_email_));
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl.h b/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl.h
index a22a66e3..fb066c1a2 100644
--- a/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl.h
+++ b/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/webui/signin/ash/signin_helper.h"
 #include "chrome/browser/ui/webui/signin/inline_login_handler.h"
 #include "components/account_manager_core/account.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 class PrefRegistrySimple;
 
@@ -63,7 +64,11 @@
   // account.
   void ShowSigninErrorPage(const std::string& email,
                            const std::string& hosted_domain);
+  void GetDeviceId(const base::Value::List& args);
 
+  // Email address provided at the start of the flow. Empty optional if no email
+  // was provided.
+  absl::optional<std::string> initial_email_;
   base::RepeatingClosure close_dialog_closure_;
   base::RepeatingCallback<void(const std::string&, const std::string&)>
       show_signin_error_;
diff --git a/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl_browsertest.cc b/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl_browsertest.cc
index b698ee4..17ac1cd 100644
--- a/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl_browsertest.cc
+++ b/chrome/browser/ui/webui/signin/ash/inline_login_handler_impl_browsertest.cc
@@ -12,10 +12,12 @@
 #include "base/test/gmock_callback_support.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
+#include "base/values.h"
 #include "chrome/browser/ash/account_manager/account_apps_availability.h"
 #include "chrome/browser/ash/account_manager/account_apps_availability_factory.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
@@ -34,6 +36,7 @@
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
 #include "components/supervised_user/core/common/pref_names.h"
 #include "components/supervised_user/core/common/supervised_user_constants.h"
+#include "components/user_manager/known_user.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "components/user_manager/user_type.h"
 #include "content/public/browser/storage_partition.h"
@@ -48,6 +51,11 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+using testing::Eq;
+using testing::IsEmpty;
+using testing::Ne;
+using testing::Not;
+
 namespace ash {
 
 namespace {
@@ -62,12 +70,14 @@
 constexpr char kSecondaryAccountOAuthCode[] = "fake_oauth_code";
 constexpr char kSecondaryAccountRefreshToken[] = "fake_refresh_token";
 constexpr char kCompleteLoginMessage[] = "completeLogin";
+constexpr char kGetDeviceIdMessage[] = "getDeviceId";
 constexpr char kMakeAvailableInArcMessage[] = "makeAvailableInArc";
 constexpr char kGetAccountsNotAvailableInArcMessage[] =
     "getAccountsNotAvailableInArc";
 constexpr char kHandleFunctionName[] = "handleFunctionName";
 constexpr char kConsentLoggedCallback[] = "consent-logged-callback";
 constexpr char kToSVersion[] = "12345678";
+constexpr char kFakeDeviceId[] = "fake-device-id";
 
 struct DeviceAccountInfo {
   std::string id;
@@ -129,6 +139,10 @@
 
   TestInlineLoginHandler(const TestInlineLoginHandler&) = delete;
   TestInlineLoginHandler& operator=(const TestInlineLoginHandler&) = delete;
+
+  void SetExtraInitParams(base::Value::Dict& params) override {
+    InlineLoginHandlerImpl::SetExtraInitParams(params);
+  }
 };
 
 class MockAccountAppsAvailabilityObserver
@@ -276,6 +290,28 @@
     web_ui()->HandleReceivedMessage("consentLogged", list_args);
   }
 
+  void SetExtraInitParamsInHandler(base::Value::Dict& dict) {
+    handler_->SetExtraInitParams(dict);
+  }
+
+  std::string GetDeviceIdFromWebview() {
+    // Call "getDeviceId".
+    base::Value::List args;
+    args.Append(kHandleFunctionName);
+    web_ui()->HandleReceivedMessage(kGetDeviceIdMessage, args);
+    base::RunLoop().RunUntilIdle();
+
+    EXPECT_THAT(web_ui()->call_data(), Not(IsEmpty()));
+    const content::TestWebUI::CallData& call_data =
+        *web_ui()->call_data().back();
+    EXPECT_EQ("cr.webUIResponse", call_data.function_name());
+    EXPECT_EQ(kHandleFunctionName, call_data.arg1()->GetString());
+    EXPECT_TRUE(call_data.arg2()->GetBool());
+
+    // Get results from JS callback.
+    return call_data.arg3()->GetString();
+  }
+
   FakeChromeUserManager* GetFakeUserManager() const {
     return static_cast<FakeChromeUserManager*>(
         user_manager::UserManager::Get());
@@ -295,8 +331,10 @@
     return identity_test_env_profile_adaptor_->identity_test_env();
   }
 
+  const AccountId& primary_account_id() { return primary_account_id_; }
+
  private:
-  std::unique_ptr<InlineLoginHandler> handler_;
+  std::unique_ptr<TestInlineLoginHandler> handler_;
   std::unique_ptr<EduCoexistenceLoginHandler> edu_handler_;
   content::TestWebUI web_ui_;
   net::EmbeddedTestServer embedded_test_server_;
@@ -354,6 +392,47 @@
   EXPECT_TRUE(future.Wait());
 }
 
+IN_PROC_BROWSER_TEST_P(InlineLoginHandlerTest,
+                       GetDeviceIdReturnsANonEmptyString) {
+  const std::string device_id = GetDeviceIdFromWebview();
+  EXPECT_THAT(device_id, Not(IsEmpty()));
+}
+
+IN_PROC_BROWSER_TEST_P(InlineLoginHandlerTest,
+                       GetDeviceIdReturnsKnownUserDeviceIdForDeviceAccount) {
+  user_manager::KnownUser known_user{g_browser_process->local_state()};
+  known_user.SetDeviceId(primary_account_id(), kFakeDeviceId);
+  base::Value::Dict params;
+  params.Set("email", primary_account_id().GetUserEmail());
+  SetExtraInitParamsInHandler(params);
+
+  EXPECT_THAT(GetDeviceIdFromWebview(), Eq(kFakeDeviceId));
+}
+
+IN_PROC_BROWSER_TEST_P(
+    InlineLoginHandlerTest,
+    GetDeviceIdDoesNotReturnKnownUserDeviceIdForSecondaryAccount) {
+  user_manager::KnownUser known_user{g_browser_process->local_state()};
+  known_user.SetDeviceId(primary_account_id(), kFakeDeviceId);
+  base::Value::Dict params;
+  params.Set("email", kSecondaryAccount1Email);
+  SetExtraInitParamsInHandler(params);
+
+  EXPECT_THAT(GetDeviceIdFromWebview(), Ne(kFakeDeviceId));
+}
+
+IN_PROC_BROWSER_TEST_P(
+    InlineLoginHandlerTest,
+    GetDeviceIdDoesNotReturnKnownUserDeviceIdForAccountAdditions) {
+  // Device Account cannot be added inline. So if an account addition is taking
+  // place, it must be for a Secondary Account - in which case, we should not
+  // generate the device id for the Device Account.
+  user_manager::KnownUser known_user{g_browser_process->local_state()};
+  known_user.SetDeviceId(primary_account_id(), kFakeDeviceId);
+
+  EXPECT_THAT(GetDeviceIdFromWebview(), Ne(kFakeDeviceId));
+}
+
 INSTANTIATE_TEST_SUITE_P(InlineLoginHandlerTestSuite,
                          InlineLoginHandlerTest,
                          ::testing::Values(GetGaiaDeviceAccountInfo(),
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 5b71040..671ecf4 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1700200460-4e0f9dc841152952e488eeceff601b56418b6b1f.profdata
+chrome-android32-main-1700222233-249e45c181224b913a989f4a352a30172342f3b7.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index 5628787..1529093 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1700200460-e2cb626996c70c7333cd9d57ec874c6a940b4ecf.profdata
+chrome-android64-main-1700222233-3e9c8f45fa79822f18ad2303465dc1e63792ba9e.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 54607b8..db994a4 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1700179073-6914a4da7273f34408d8714fbaeed66e7e524d14.profdata
+chrome-linux-main-1700200460-26320b39e0e9878f10532c5aca6101870e73c1fe.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 6da3c5b..d7e5fdc 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1700179073-7e571b8d57a5229e1441534301e8f05fd8229b8a.profdata
+chrome-mac-main-1700200460-b2785c7a18bdeecb374c74c36af6fddb09cf1d47.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index acf953a..09c3222 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1700179073-c8b6b87eb48ced3f1d81089a86f219abc814e1ab.profdata
+chrome-win-arm64-main-1700222233-d66f4039907d67e5a1d9bcbc0e13fd389f617109.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 330e270..29ed9e2 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1700189836-043f10b0882b755e15e9c5e3c2bf9543d0f24685.profdata
+chrome-win32-main-1700211402-4e8e1432192385b257a78e94c619a0237e41d606.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 934c211b..91afea2 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1700168109-0517d1f04e4a45602892f1818929c0c47ec4aa6c.profdata
+chrome-win64-main-1700200460-5fb5e79220f551eca1b787ee10331330f66dcdb8.profdata
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index f05d982..4ddecea 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -3596,10 +3596,8 @@
 inline constexpr char kSharingFCMRegistration[] = "sharing.fcm_registration";
 inline constexpr char kSharingLocalSharingInfo[] = "sharing.local_sharing_info";
 
-#if !BUILDFLAG(IS_ANDROID)
-// Dictionary that contains all of the Hats Survey Metadata.
+// Dictionary that contains all of the Hats Survey Metadata for desktop surveys.
 inline constexpr char kHatsSurveyMetadata[] = "hats.survey_metadata";
-#endif  // !BUILDFLAG(IS_ANDROID)
 
 inline constexpr char kExternalProtocolDialogShowAlwaysOpenCheckbox[] =
     "external_protocol_dialog.show_always_open_checkbox";
diff --git a/chrome/renderer/autofill/form_autofill_browsertest.cc b/chrome/renderer/autofill/form_autofill_browsertest.cc
index 6f9933c5d..4cbcc99 100644
--- a/chrome/renderer/autofill/form_autofill_browsertest.cc
+++ b/chrome/renderer/autofill/form_autofill_browsertest.cc
@@ -2339,7 +2339,6 @@
   ASSERT_NE(nullptr, frame);
 
   WebFormControlElement element = GetFormControlElementById("element");
-  element.SetSelectionRange(1, 4);
 
   FormFieldData result1;
   WebFormControlElementToFormField(WebFormElement(), element, nullptr,
@@ -2354,8 +2353,6 @@
 
   expected.value.clear();
   EXPECT_FORM_FIELD_DATA_EQUALS(expected, result1);
-  EXPECT_EQ(0u, result1.selection_start);
-  EXPECT_EQ(0u, result1.selection_end);
 
   FormFieldData result2;
   WebFormControlElementToFormField(WebFormElement(), element, nullptr,
@@ -2363,10 +2360,6 @@
 
   expected.value = u"value";
   EXPECT_FORM_FIELD_DATA_EQUALS(expected, result2);
-  EXPECT_EQ(1u, result2.selection_start);
-  EXPECT_EQ(4u, result2.selection_end);
-  EXPECT_EQ(u"alu", result2.GetSelection());
-  EXPECT_EQ(u"alu", result2.GetSelectionAsStringView());
 }
 
 // We should be able to extract a text field with autocomplete="off".
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index deb21d3..197cf6b 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3112,6 +3112,7 @@
         "../browser/browser_process_platform_part_chromeos_browsertest.cc",
         "../browser/certificate_provider/test_certificate_provider_extension_mixin.cc",
         "../browser/certificate_provider/test_certificate_provider_extension_mixin.h",
+        "../browser/chromeos/enterprise/cloud_storage/one_drive_pref_observer_browsertest.cc",
         "../browser/chromeos/enterprise/incognito_navigation_throttle_browsertest.cc",
         "../browser/chromeos/policy/default_notifications_setting_browsertest.cc",
         "../browser/chromeos/policy/dino_easter_egg_browsertest.cc",
@@ -4510,6 +4511,7 @@
         "../browser/ash/login/screens/os_trial_screen_browsertest.cc",
         "../browser/ash/login/screens/osauth/cryptohome_recovery_screen_browsertest.cc",
         "../browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen_browsertest.cc",
+        "../browser/ash/login/screens/osauth/password_selection_screen_browsertest.cc",
         "../browser/ash/login/screens/osauth/recovery_eligibility_screen_browsertest.cc",
         "../browser/ash/login/screens/packaged_license_screen_browsertest.cc",
         "../browser/ash/login/screens/parental_handoff_screen_browsertest.cc",
@@ -9852,15 +9854,12 @@
       "../browser/ui/views/autofill/popup/mock_selection_delegate.h",
       "../browser/ui/views/autofill/popup/popup_row_content_view_unittest.cc",
       "../browser/ui/views/autofill/popup/popup_row_factory_utils_unittest.cc",
-      "../browser/ui/views/autofill/popup/popup_row_strategy_unittest.cc",
       "../browser/ui/views/autofill/popup/popup_row_view_unittest.cc",
       "../browser/ui/views/autofill/popup/popup_row_with_button_view_unittest.cc",
       "../browser/ui/views/autofill/popup/popup_separator_view_unittest.cc",
       "../browser/ui/views/autofill/popup/popup_view_utils_unittest.cc",
       "../browser/ui/views/autofill/popup/popup_view_views_unittest.cc",
       "../browser/ui/views/autofill/popup/popup_warning_view_unittest.cc",
-      "../browser/ui/views/autofill/popup/test_popup_row_strategy.cc",
-      "../browser/ui/views/autofill/popup/test_popup_row_strategy.h",
       "../browser/ui/views/bookmarks/bookmark_bar_view_test_helper.h",
       "../browser/ui/views/bookmarks/bookmark_bar_view_unittest.cc",
       "../browser/ui/views/bookmarks/bookmark_bubble_view_unittest.cc",
@@ -10007,6 +10006,7 @@
     if (is_mac) {
       sources += [
         "../browser/media_galleries/mac/mtp_device_delegate_impl_mac_unittest.mm",
+        "../browser/ui/passwords/bubble_controllers/relaunch_chrome_bubble_controller_unittest.cc",
         "../browser/ui/views/accelerator_table_unittest_mac.mm",
         "../browser/ui/views/frame/browser_non_client_frame_view_mac_unittest.mm",
       ]
diff --git a/chromeos/ash/components/osauth/impl/auth_session_storage_impl.cc b/chromeos/ash/components/osauth/impl/auth_session_storage_impl.cc
index 1a7829c..17849f5 100644
--- a/chromeos/ash/components/osauth/impl/auth_session_storage_impl.cc
+++ b/chromeos/ash/components/osauth/impl/auth_session_storage_impl.cc
@@ -116,6 +116,11 @@
         FROM_HERE, base::BindOnce(std::move(callback), nullptr));
     return;
   }
+  if (data_it->second->withdraw_callback) {
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), nullptr));
+    return;
+  }
   data_it->second->borrow_queue.emplace(location, std::move(callback));
 }
 
@@ -146,6 +151,23 @@
     Invalidate(token, absl::nullopt);
     return;
   }
+
+  if (data_it->second->withdraw_callback) {
+    CHECK(data_it->second->context);
+    auto stored_context = std::move(data_it->second->context);
+    auto callback = std::move(data_it->second->withdraw_callback.value());
+    // Invalidation queue should be handled by condition above,
+    // and borrow queue should be empty per invariant
+    // in BorrowAsync/Withdraw methods.
+    CHECK(data_it->second->borrow_queue.empty());
+    CHECK(data_it->second->invalidation_queue.empty());
+    tokens_.erase(data_it);
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE,
+        base::BindOnce(std::move(callback), std::move(stored_context)));
+    return;
+  }
+
   if (data_it->second->keep_alive_counter > 0) {
     HandleSessionRefresh(token);
     auto check_still_alive = tokens_.find(token);
@@ -167,6 +189,47 @@
   }
 }
 
+void AuthSessionStorageImpl::Withdraw(const AuthProofToken& token,
+                                      BorrowCallback callback) {
+  auto data_it = tokens_.find(token);
+  if (data_it == std::end(tokens_)) {
+    LOG(ERROR) << "Accessing expired token";
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), nullptr));
+    return;
+  }
+  if (data_it->second->state == TokenState::kOwned) {
+    CHECK(data_it->second->context);
+    auto context = std::move(data_it->second->context);
+    // As context is owned, there should be no waiting borrow/invalidate
+    // callbacks.
+    CHECK(data_it->second->borrow_queue.empty());
+    CHECK(data_it->second->invalidation_queue.empty());
+    tokens_.erase(data_it);
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), std::move(context)));
+    return;
+  }
+
+  if (data_it->second->state == TokenState::kInvalidating ||
+      data_it->second->invalidate_on_return) {
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), nullptr));
+    return;
+  }
+  CHECK_EQ(data_it->second->state, TokenState::kBorrowed);
+  // Drain borrow queue.
+  while (!data_it->second->borrow_queue.empty()) {
+    std::pair<base::Location, BorrowCallback> pending_borrow =
+        std::move(data_it->second->borrow_queue.front());
+    data_it->second->borrow_queue.pop();
+    std::move(pending_borrow.second).Run(nullptr);
+  }
+
+  CHECK(!data_it->second->withdraw_callback) << "There can be only one!";
+  data_it->second->withdraw_callback = std::move(callback);
+}
+
 void AuthSessionStorageImpl::Invalidate(
     const AuthProofToken& token,
     absl::optional<InvalidationCallback> on_invalidated) {
diff --git a/chromeos/ash/components/osauth/impl/auth_session_storage_impl.h b/chromeos/ash/components/osauth/impl/auth_session_storage_impl.h
index 59ca6e0..6c12843 100644
--- a/chromeos/ash/components/osauth/impl/auth_session_storage_impl.h
+++ b/chromeos/ash/components/osauth/impl/auth_session_storage_impl.h
@@ -61,6 +61,7 @@
   const UserContext* Peek(const AuthProofToken& token) override;
   void Return(const AuthProofToken& token,
               std::unique_ptr<UserContext> context) override;
+  void Withdraw(const AuthProofToken& token, BorrowCallback callback) override;
   void Invalidate(const AuthProofToken& token,
                   absl::optional<InvalidationCallback> on_invalidated) override;
   std::unique_ptr<ScopedSessionRefresher> KeepAlive(
@@ -90,6 +91,7 @@
     bool invalidate_on_return = false;
 
     std::queue<InvalidationCallback> invalidation_queue;
+    absl::optional<BorrowCallback> withdraw_callback;
     std::queue<std::pair<base::Location, BorrowCallback>> borrow_queue;
 
     // Timer to perform next action (extending or invalidating session).
diff --git a/chromeos/ash/components/osauth/impl/auth_session_storage_impl_unittest.cc b/chromeos/ash/components/osauth/impl/auth_session_storage_impl_unittest.cc
index 78a5c586..a7b2f905 100644
--- a/chromeos/ash/components/osauth/impl/auth_session_storage_impl_unittest.cc
+++ b/chromeos/ash/components/osauth/impl/auth_session_storage_impl_unittest.cc
@@ -294,4 +294,50 @@
   ASSERT_FALSE(storage_->IsValid(token));
 }
 
+TEST_F(AuthSessionStorageImplTest, WithdrawTest) {
+  AuthProofToken token = storage_->Store(CreateContext());
+  ASSERT_TRUE(storage_->IsValid(token));
+
+  base::test::TestFuture<std::unique_ptr<UserContext>> withdraw_future;
+
+  storage_->Withdraw(token, withdraw_future.GetCallback());
+
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(withdraw_future.IsReady());
+  ASSERT_NE(withdraw_future.Get().get(), nullptr);
+
+  ASSERT_FALSE(storage_->IsValid(token));
+}
+
+TEST_F(AuthSessionStorageImplTest, WithdrawPreceedsBorrow) {
+  AuthProofToken token = storage_->Store(CreateContext());
+  ASSERT_TRUE(storage_->IsValid(token));
+
+  // Borrow context, token is still valid.
+  auto context = storage_->BorrowForTests(FROM_HERE, token);
+  ASSERT_TRUE(storage_->IsValid(token));
+
+  base::test::TestFuture<std::unique_ptr<UserContext>> borrow_future;
+  base::test::TestFuture<std::unique_ptr<UserContext>> withdraw_future;
+
+  storage_->BorrowAsync(FROM_HERE, token, borrow_future.GetCallback());
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(borrow_future.IsReady());
+
+  storage_->Withdraw(token, withdraw_future.GetCallback());
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(withdraw_future.IsReady());
+
+  // Return context, token still valid
+  storage_->Return(token, std::move(context));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(withdraw_future.IsReady());
+  ASSERT_TRUE(borrow_future.IsReady());
+
+  ASSERT_EQ(borrow_future.Get().get(), nullptr);
+  ASSERT_NE(withdraw_future.Get().get(), nullptr);
+  ASSERT_FALSE(storage_->IsValid(token));
+}
+
 }  // namespace ash
\ No newline at end of file
diff --git a/chromeos/ash/components/osauth/public/auth_session_storage.h b/chromeos/ash/components/osauth/public/auth_session_storage.h
index 20b3535ab..5853417 100644
--- a/chromeos/ash/components/osauth/public/auth_session_storage.h
+++ b/chromeos/ash/components/osauth/public/auth_session_storage.h
@@ -81,6 +81,21 @@
                            const AuthProofToken& token,
                            BorrowCallback callback) = 0;
 
+  // Allows client to obtain UserContext without intent to return it back.
+  // Takes precedence over Borrow requests, but not over invalidate
+  // request.
+  // Withdrawing context from the storage makes associated token invalid.
+  //
+  // If context is borrowed at the moment of the call, the callback
+  // would be called once the context is returned to the storage.
+  // Note that callback might be called with `null` value, if
+  // the context would become invalid before it is returned.
+  //
+  // There can be only one Withdraw request at one time, requesting parallel
+  // Withdraw request would result in crash.
+  virtual void Withdraw(const AuthProofToken& token,
+                        BorrowCallback callback) = 0;
+
   // Allows to inspect stored UserContext. The reference is only valid within
   // same UI event, and should not be stored by caller.
   virtual const UserContext* Peek(const AuthProofToken& token) = 0;
diff --git a/chromeos/ash/services/auth_factor_config/auth_factor_config.cc b/chromeos/ash/services/auth_factor_config/auth_factor_config.cc
index ba70fc9f..2b3d9b75 100644
--- a/chromeos/ash/services/auth_factor_config/auth_factor_config.cc
+++ b/chromeos/ash/services/auth_factor_config/auth_factor_config.cc
@@ -106,9 +106,7 @@
   const cryptohome::AuthFactorsSet cryptohome_supported_factors =
       context->GetAuthFactorsConfiguration().get_supported_factors();
 
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
-  }
+  ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
 
   switch (factor) {
     case mojom::AuthFactor::kRecovery: {
@@ -159,9 +157,7 @@
     return;
   }
   const auto& config = context->GetAuthFactorsConfiguration();
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
-  }
+  ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
 
   switch (factor) {
     case mojom::AuthFactor::kRecovery: {
@@ -286,9 +282,7 @@
   }
   const auto& config = context->GetAuthFactorsConfiguration();
 
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
-  }
+  ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
 
   switch (factor) {
     case mojom::AuthFactor::kRecovery: {
@@ -364,19 +358,6 @@
 void AuthFactorConfig::ObtainContext(
     const std::string& auth_token,
     base::OnceCallback<void(std::unique_ptr<UserContext>)> callback) {
-  if (!ash::features::ShouldUseAuthSessionStorage()) {
-    const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
-    CHECK(user);
-    auto* user_context_ptr =
-        quick_unlock_storage_->GetUserContext(user, auth_token);
-    if (!user_context_ptr) {
-      std::move(callback).Run(nullptr);
-      return;
-    }
-    std::move(callback).Run(std::make_unique<UserContext>(*user_context_ptr));
-    return;
-  }
-
   if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
     std::move(callback).Run(nullptr);
     return;
@@ -396,9 +377,9 @@
           cryptohome::AuthFactorType::kPassword) ||
       context->GetAuthFactorsConfiguration().HasConfiguredFactor(
           cryptohome::AuthFactorType::kPin);
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
-  }
+
+  ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
+
   if (error.has_value()) {
     LOG(ERROR) << "Refreshing list of configured auth factors failed, code "
                << error->get_cryptohome_code();
@@ -410,12 +391,6 @@
     OnUserHasKnowledgeFactor(*context);
   }
 
-  if (!ash::features::ShouldUseAuthSessionStorage()) {
-    const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
-    quick_unlock_storage_->SetUserContext(user, std::move(context));
-  }
-
-
   std::move(callback).Run(mojom::ConfigureResult::kSuccess);
 
   for (auto& observer : observers_) {
diff --git a/chromeos/ash/services/auth_factor_config/in_process_instances.cc b/chromeos/ash/services/auth_factor_config/in_process_instances.cc
index b70121f..072debf 100644
--- a/chromeos/ash/services/auth_factor_config/in_process_instances.cc
+++ b/chromeos/ash/services/auth_factor_config/in_process_instances.cc
@@ -27,24 +27,30 @@
 RecoveryFactorEditor& GetRecoveryFactorEditorImpl(
     QuickUnlockStorageDelegate& delegate,
     PrefService* local_state) {
+  // TODO(b/271249180): cleanup remaining QuickUnlockStorageDelegate refs
+  // we don't need it anymore.
   static base::NoDestructor<RecoveryFactorEditor> recovery_factor_editor(
-      &GetAuthFactorConfigImpl(delegate, local_state), &delegate);
+      &GetAuthFactorConfigImpl(delegate, local_state));
   return *recovery_factor_editor;
 }
 
 PinFactorEditor& GetPinFactorEditorImpl(QuickUnlockStorageDelegate& storage,
                                         PrefService* local_state,
                                         PinBackendDelegate& pin_backend) {
+  // TODO(b/271249180): cleanup remaining QuickUnlockStorageDelegate refs
+  // we don't need it anymore.
   static base::NoDestructor<PinFactorEditor> pin_factor_editor(
-      &GetAuthFactorConfigImpl(storage, local_state), &pin_backend, &storage);
+      &GetAuthFactorConfigImpl(storage, local_state), &pin_backend);
   return *pin_factor_editor;
 }
 
 PasswordFactorEditor& GetPasswordFactorEditorImpl(
     QuickUnlockStorageDelegate& storage,
     PrefService* local_state) {
+  // TODO(b/271249180): cleanup remaining QuickUnlockStorageDelegate refs
+  // we don't need it anymore.
   static base::NoDestructor<PasswordFactorEditor> password_factor_editor(
-      &GetAuthFactorConfigImpl(storage, local_state), &storage);
+      &GetAuthFactorConfigImpl(storage, local_state));
   return *password_factor_editor;
 }
 
diff --git a/chromeos/ash/services/auth_factor_config/password_factor_editor.cc b/chromeos/ash/services/auth_factor_config/password_factor_editor.cc
index 6744751f..5308c5ce 100644
--- a/chromeos/ash/services/auth_factor_config/password_factor_editor.cc
+++ b/chromeos/ash/services/auth_factor_config/password_factor_editor.cc
@@ -46,13 +46,10 @@
 
 }  // namespace
 
-PasswordFactorEditor::PasswordFactorEditor(AuthFactorConfig* auth_factor_config,
-                                           QuickUnlockStorageDelegate* storage)
+PasswordFactorEditor::PasswordFactorEditor(AuthFactorConfig* auth_factor_config)
     : auth_factor_config_(auth_factor_config),
-      quick_unlock_storage_(storage),
       auth_factor_editor_(UserDataAuthClient::Get()) {
   CHECK(auth_factor_config_);
-  CHECK(quick_unlock_storage_);
 }
 
 PasswordFactorEditor::~PasswordFactorEditor() = default;
@@ -69,71 +66,34 @@
     return;
   }
 
-  std::unique_ptr<UserContext> user_context;
-
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
-      LOG(ERROR) << "Invalid auth token";
-      std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
-      return;
-    }
-    ash::AuthSessionStorage::Get()->BorrowAsync(
-        FROM_HERE, auth_token,
-        base::BindOnce(&PasswordFactorEditor::UpdatePasswordWithContext,
-                       weak_factory_.GetWeakPtr(), auth_token, new_password,
-                       cryptohome::KeyLabel{kCryptohomeLocalPasswordKeyLabel},
-                       std::move(callback)));
-    return;
-  }
-
-  const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
-  CHECK(user);
-  auto* user_context_ptr =
-      quick_unlock_storage_->GetUserContext(user, auth_token);
-  if (user_context_ptr == nullptr) {
+  if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
     LOG(ERROR) << "Invalid auth token";
     std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
     return;
   }
-  UpdatePasswordWithContext(
-      auth_token, new_password,
-      cryptohome::KeyLabel{kCryptohomeLocalPasswordKeyLabel},
-      std::move(callback), std::make_unique<UserContext>(*user_context_ptr));
+  ash::AuthSessionStorage::Get()->BorrowAsync(
+      FROM_HERE, auth_token,
+      base::BindOnce(&PasswordFactorEditor::UpdatePasswordWithContext,
+                     weak_factory_.GetWeakPtr(), auth_token, new_password,
+                     cryptohome::KeyLabel{kCryptohomeLocalPasswordKeyLabel},
+                     std::move(callback)));
 }
 
 void PasswordFactorEditor::UpdateOnlinePassword(
     const std::string& auth_token,
     const std::string& new_password,
     base::OnceCallback<void(mojom::ConfigureResult)> callback) {
-  std::unique_ptr<UserContext> user_context;
-
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
-      LOG(ERROR) << "Invalid auth token";
-      std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
-      return;
-    }
-    ash::AuthSessionStorage::Get()->BorrowAsync(
-        FROM_HERE, auth_token,
-        base::BindOnce(&PasswordFactorEditor::UpdatePasswordWithContext,
-                       weak_factory_.GetWeakPtr(), auth_token, new_password,
-                       cryptohome::KeyLabel{kCryptohomeGaiaKeyLabel},
-                       std::move(callback)));
-    return;
-  }
-
-  const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
-  CHECK(user);
-  auto* user_context_ptr =
-      quick_unlock_storage_->GetUserContext(user, auth_token);
-  if (user_context_ptr == nullptr) {
+  if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
     LOG(ERROR) << "Invalid auth token";
     std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
     return;
   }
-  UpdatePasswordWithContext(
-      auth_token, new_password, cryptohome::KeyLabel{kCryptohomeGaiaKeyLabel},
-      std::move(callback), std::make_unique<UserContext>(*user_context_ptr));
+  ash::AuthSessionStorage::Get()->BorrowAsync(
+      FROM_HERE, auth_token,
+      base::BindOnce(&PasswordFactorEditor::UpdatePasswordWithContext,
+                     weak_factory_.GetWeakPtr(), auth_token, new_password,
+                     cryptohome::KeyLabel{kCryptohomeGaiaKeyLabel},
+                     std::move(callback)));
 }
 
 void PasswordFactorEditor::SetLocalPassword(
@@ -148,71 +108,34 @@
     return;
   }
 
-  std::unique_ptr<UserContext> user_context;
-
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
-      LOG(ERROR) << "Invalid auth token";
-      std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
-      return;
-    }
-    ash::AuthSessionStorage::Get()->BorrowAsync(
-        FROM_HERE, auth_token,
-        base::BindOnce(&PasswordFactorEditor::SetPasswordWithContext,
-                       weak_factory_.GetWeakPtr(), auth_token, new_password,
-                       cryptohome::KeyLabel{kCryptohomeLocalPasswordKeyLabel},
-                       std::move(callback)));
-    return;
-  }
-
-  const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
-  CHECK(user);
-  auto* user_context_ptr =
-      quick_unlock_storage_->GetUserContext(user, auth_token);
-  if (user_context_ptr == nullptr) {
+  if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
     LOG(ERROR) << "Invalid auth token";
     std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
     return;
   }
-  SetPasswordWithContext(auth_token, new_password,
-                         cryptohome::KeyLabel{kCryptohomeLocalPasswordKeyLabel},
-                         std::move(callback),
-                         std::make_unique<UserContext>(*user_context_ptr));
+  ash::AuthSessionStorage::Get()->BorrowAsync(
+      FROM_HERE, auth_token,
+      base::BindOnce(&PasswordFactorEditor::SetPasswordWithContext,
+                     weak_factory_.GetWeakPtr(), auth_token, new_password,
+                     cryptohome::KeyLabel{kCryptohomeLocalPasswordKeyLabel},
+                     std::move(callback)));
 }
 
 void PasswordFactorEditor::SetOnlinePassword(
     const std::string& auth_token,
     const std::string& new_password,
     base::OnceCallback<void(mojom::ConfigureResult)> callback) {
-  std::unique_ptr<UserContext> user_context;
-
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
-      LOG(ERROR) << "Invalid auth token";
-      std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
-      return;
-    }
-    ash::AuthSessionStorage::Get()->BorrowAsync(
-        FROM_HERE, auth_token,
-        base::BindOnce(&PasswordFactorEditor::SetPasswordWithContext,
-                       weak_factory_.GetWeakPtr(), auth_token, new_password,
-                       cryptohome::KeyLabel{kCryptohomeGaiaKeyLabel},
-                       std::move(callback)));
-    return;
-  }
-
-  const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
-  CHECK(user);
-  auto* user_context_ptr =
-      quick_unlock_storage_->GetUserContext(user, auth_token);
-  if (user_context_ptr == nullptr) {
+  if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
     LOG(ERROR) << "Invalid auth token";
     std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
     return;
   }
-  SetPasswordWithContext(
-      auth_token, new_password, cryptohome::KeyLabel{kCryptohomeGaiaKeyLabel},
-      std::move(callback), std::make_unique<UserContext>(*user_context_ptr));
+  ash::AuthSessionStorage::Get()->BorrowAsync(
+      FROM_HERE, auth_token,
+      base::BindOnce(&PasswordFactorEditor::SetPasswordWithContext,
+                     weak_factory_.GetWeakPtr(), auth_token, new_password,
+                     cryptohome::KeyLabel{kCryptohomeGaiaKeyLabel},
+                     std::move(callback)));
 }
 
 void PasswordFactorEditor::UpdatePasswordWithContext(
diff --git a/chromeos/ash/services/auth_factor_config/password_factor_editor.h b/chromeos/ash/services/auth_factor_config/password_factor_editor.h
index 4f6d8158c..d252b5b 100644
--- a/chromeos/ash/services/auth_factor_config/password_factor_editor.h
+++ b/chromeos/ash/services/auth_factor_config/password_factor_editor.h
@@ -20,8 +20,7 @@
 
 class PasswordFactorEditor : public mojom::PasswordFactorEditor {
  public:
-  PasswordFactorEditor(AuthFactorConfig* auth_factor_config,
-                       QuickUnlockStorageDelegate* storage);
+  explicit PasswordFactorEditor(AuthFactorConfig* auth_factor_config);
   ~PasswordFactorEditor() override;
 
   PasswordFactorEditor(const mojom::PasswordFactorEditor&) = delete;
@@ -76,7 +75,6 @@
       absl::optional<AuthenticationError> error);
 
   raw_ptr<AuthFactorConfig> auth_factor_config_;
-  raw_ptr<QuickUnlockStorageDelegate> quick_unlock_storage_;
   mojo::ReceiverSet<mojom::PasswordFactorEditor> receivers_;
   AuthFactorEditor auth_factor_editor_;
   base::WeakPtrFactory<PasswordFactorEditor> weak_factory_{this};
diff --git a/chromeos/ash/services/auth_factor_config/pin_factor_editor.cc b/chromeos/ash/services/auth_factor_config/pin_factor_editor.cc
index f506fee0..1fa0c166 100644
--- a/chromeos/ash/services/auth_factor_config/pin_factor_editor.cc
+++ b/chromeos/ash/services/auth_factor_config/pin_factor_editor.cc
@@ -14,15 +14,12 @@
 namespace ash::auth {
 
 PinFactorEditor::PinFactorEditor(AuthFactorConfig* auth_factor_config,
-                                 PinBackendDelegate* pin_backend,
-                                 QuickUnlockStorageDelegate* storage)
+                                 PinBackendDelegate* pin_backend)
     : auth_factor_config_(auth_factor_config),
       pin_backend_(pin_backend),
-      quick_unlock_storage_(storage),
       auth_factor_editor_(UserDataAuthClient::Get()) {
   CHECK(auth_factor_config_);
   CHECK(pin_backend_);
-  CHECK(quick_unlock_storage_);
 }
 
 PinFactorEditor::~PinFactorEditor() = default;
@@ -49,18 +46,6 @@
 void PinFactorEditor::ObtainContext(
     const std::string& auth_token,
     base::OnceCallback<void(std::unique_ptr<UserContext>)> callback) {
-  if (!ash::features::ShouldUseAuthSessionStorage()) {
-    const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
-    CHECK(user);
-    auto* user_context_ptr =
-        quick_unlock_storage_->GetUserContext(user, auth_token);
-    if (!user_context_ptr) {
-      std::move(callback).Run(nullptr);
-      return;
-    }
-    std::move(callback).Run(std::make_unique<UserContext>(*user_context_ptr));
-    return;
-  }
 
   if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
     std::move(callback).Run(nullptr);
@@ -81,9 +66,7 @@
     return;
   }
   AccountId account_id = context->GetAccountId();
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
-  }
+  ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
   pin_backend_->Set(account_id, auth_token, pin,
                     base::BindOnce(&PinFactorEditor::OnPinConfigured,
                                    weak_factory_.GetWeakPtr(), auth_token,
@@ -102,9 +85,7 @@
 
   AccountId account_id = context->GetAccountId();
 
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
-  }
+  ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
   auth_factor_config_->IsConfigured(
       auth_token, mojom::AuthFactor::kPin,
       base::BindOnce(&PinFactorEditor::OnIsPinConfiguredForRemove,
diff --git a/chromeos/ash/services/auth_factor_config/pin_factor_editor.h b/chromeos/ash/services/auth_factor_config/pin_factor_editor.h
index ccd8c85..f2816be 100644
--- a/chromeos/ash/services/auth_factor_config/pin_factor_editor.h
+++ b/chromeos/ash/services/auth_factor_config/pin_factor_editor.h
@@ -17,9 +17,7 @@
 // The implementation of the PinFactorEditor mojo service.
 class PinFactorEditor : public mojom::PinFactorEditor {
  public:
-  PinFactorEditor(AuthFactorConfig*,
-                  PinBackendDelegate* pin_backend,
-                  QuickUnlockStorageDelegate* storage);
+  PinFactorEditor(AuthFactorConfig*, PinBackendDelegate* pin_backend);
   ~PinFactorEditor() override;
 
   PinFactorEditor(const PinFactorEditor&) = delete;
@@ -65,7 +63,6 @@
 
   raw_ptr<AuthFactorConfig> auth_factor_config_;
   raw_ptr<PinBackendDelegate> pin_backend_;
-  raw_ptr<QuickUnlockStorageDelegate> quick_unlock_storage_;
   mojo::ReceiverSet<mojom::PinFactorEditor> receivers_;
   AuthFactorEditor auth_factor_editor_;
   base::WeakPtrFactory<PinFactorEditor> weak_factory_{this};
diff --git a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc
index e0aaf91..48e078b 100644
--- a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc
+++ b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.cc
@@ -14,14 +14,10 @@
 
 namespace ash::auth {
 
-RecoveryFactorEditor::RecoveryFactorEditor(
-    AuthFactorConfig* auth_factor_config,
-    QuickUnlockStorageDelegate* quick_unlock_storage)
+RecoveryFactorEditor::RecoveryFactorEditor(AuthFactorConfig* auth_factor_config)
     : auth_factor_config_(auth_factor_config),
-      quick_unlock_storage_(quick_unlock_storage),
       auth_factor_editor_(UserDataAuthClient::Get()) {
   DCHECK(auth_factor_config_);
-  DCHECK(quick_unlock_storage_);
 }
 RecoveryFactorEditor::~RecoveryFactorEditor() = default;
 
@@ -54,30 +50,16 @@
     return;
   }
 
-  if (ash::features::ShouldUseAuthSessionStorage()) {
-    if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
-      LOG(ERROR) << "Invalid auth token";
-      std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
-      return;
-    }
-    ash::AuthSessionStorage::Get()->BorrowAsync(
-        FROM_HERE, auth_token,
-        base::BindOnce(&RecoveryFactorEditor::ConfigureWithContext,
-                       weak_factory_.GetWeakPtr(), auth_token, should_enable,
-                       std::move(callback), is_editable));
-    return;
-  }
-  const auto* user = ::user_manager::UserManager::Get()->GetPrimaryUser();
-  auto* user_context_ptr =
-      quick_unlock_storage_->GetUserContext(user, auth_token);
-  if (user_context_ptr == nullptr) {
+  if (!ash::AuthSessionStorage::Get()->IsValid(auth_token)) {
     LOG(ERROR) << "Invalid auth token";
     std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
     return;
   }
-  ConfigureWithContext(auth_token, should_enable, std::move(callback),
-                       is_editable,
-                       std::make_unique<UserContext>(*user_context_ptr));
+  ash::AuthSessionStorage::Get()->BorrowAsync(
+      FROM_HERE, auth_token,
+      base::BindOnce(&RecoveryFactorEditor::ConfigureWithContext,
+                     weak_factory_.GetWeakPtr(), auth_token, should_enable,
+                     std::move(callback), is_editable));
 }
 
 void RecoveryFactorEditor::ConfigureWithContext(
@@ -91,10 +73,7 @@
           cryptohome::AuthFactorType::kRecovery);
 
   if (should_enable == currently_enabled) {
-    if (ash::features::ShouldUseAuthSessionStorage()) {
-      ash::AuthSessionStorage::Get()->Return(auth_token,
-                                             std::move(user_context));
-    }
+    ash::AuthSessionStorage::Get()->Return(auth_token, std::move(user_context));
     std::move(callback).Run(mojom::ConfigureResult::kSuccess);
     return;
   }
@@ -121,11 +100,8 @@
     if (error->get_cryptohome_code() ==
         user_data_auth::CRYPTOHOME_INVALID_AUTH_SESSION_TOKEN) {
       // Handle expired auth session gracefully.
-      if (ash::features::ShouldUseAuthSessionStorage()) {
-        ash::AuthSessionStorage::Get()->Invalidate(auth_token,
-                                                   base::DoNothing());
-        ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
-      }
+      ash::AuthSessionStorage::Get()->Return(auth_token, std::move(context));
+      ash::AuthSessionStorage::Get()->Invalidate(auth_token, base::DoNothing());
       std::move(callback).Run(mojom::ConfigureResult::kInvalidTokenError);
       return;
     }
diff --git a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h
index fc80afb..fefa31c 100644
--- a/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h
+++ b/chromeos/ash/services/auth_factor_config/recovery_factor_editor.h
@@ -18,7 +18,7 @@
 // The implementation of the RecoveryFactorEditor service.
 class RecoveryFactorEditor : public mojom::RecoveryFactorEditor {
  public:
-  explicit RecoveryFactorEditor(AuthFactorConfig*, QuickUnlockStorageDelegate*);
+  explicit RecoveryFactorEditor(AuthFactorConfig*);
   RecoveryFactorEditor(const RecoveryFactorEditor&) = delete;
   RecoveryFactorEditor& operator=(const RecoveryFactorEditor&) = delete;
   ~RecoveryFactorEditor() override;
@@ -48,7 +48,6 @@
       absl::optional<AuthenticationError> error);
 
   raw_ptr<AuthFactorConfig> auth_factor_config_;
-  raw_ptr<QuickUnlockStorageDelegate> quick_unlock_storage_;
   AuthFactorEditor auth_factor_editor_;
   mojo::ReceiverSet<mojom::RecoveryFactorEditor> receivers_;
   base::WeakPtrFactory<RecoveryFactorEditor> weak_factory_{this};
diff --git a/clank b/clank
index 558518c5..3349b81 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 558518c5aafdd8733412f00a962739724caea90f
+Subproject commit 3349b81469925ee73aa3f70a67af7f485f9798c6
diff --git a/components/autofill/OWNERS b/components/autofill/OWNERS
index 738b63c..c927a49 100644
--- a/components/autofill/OWNERS
+++ b/components/autofill/OWNERS
@@ -2,5 +2,7 @@
 koerber@google.com
 schwering@google.com
 fleimgruber@google.com
+jkeitel@google.com
 pkotwicz@chromium.org
 smcgruer@chromium.org
+rouslan@chromium.org
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 7c9ef23..060b309 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -5,7 +5,6 @@
 #include "components/autofill/content/renderer/autofill_agent.h"
 
 #include <stddef.h>
-
 #include <optional>
 #include <tuple>
 
@@ -1465,67 +1464,83 @@
     const WebFormElement& form,
     const WebFormControlElement& element,
     ElementChangeSource source) {
-  if (source == ElementChangeSource::WILL_SEND_SUBMIT_EVENT) {
-    // Fire the form submission event to avoid missing submission when web site
-    // handles the onsubmit event, this also gets the form before Javascript
-    // could change it.
-    // We don't clear submitted_forms_ because OnFormSubmitted will normally be
-    // invoked afterwards and we don't want to fire the same event twice.
-    FireHostSubmitEvents(form, /*known_success=*/false,
-                         SubmissionSource::FORM_SUBMISSION);
-    ResetLastInteractedElements();
-  } else if (source == ElementChangeSource::TEXTFIELD_CHANGED ||
-             source == ElementChangeSource::SELECT_CHANGED) {
-    // Remember the last form the user interacted with.
-    if (!element.Form().IsNull()) {
-      UpdateLastInteractedForm(element.Form());
-    } else {
-      // Remove visible elements.
-      if (!unsafe_render_frame()) {
-        return;
-      }
-      WebDocument doc = unsafe_render_frame()->GetWebFrame()->GetDocument();
-      if (!doc.IsNull()) {
-        std::erase_if(
-            formless_elements_user_edited_,
-            [&doc](const FieldRendererId field_id) {
-              WebFormControlElement field =
-                  form_util::FindFormControlByRendererId(
-                      doc, field_id, /*form_to_be_searched =*/FormRendererId());
-              return !field.IsNull() &&
-                     form_util::IsWebElementFocusableForAutofill(field);
-            });
-      }
-      formless_elements_user_edited_.insert(
-          form_util::GetFieldRendererId(element));
-      provisionally_saved_form_ = CollectFormlessElements();
-      if (provisionally_saved_form_) {
-        last_interacted_form_.Reset();
-      }
+  // Updates cached data needed for submission so that we only cache the latest
+  // version of the to-be-submitted form.
+  auto update_submission_data_on_user_edit = [&]() {
+    // If dealing with a form, forward directly to UpdateLastInteractedForm. The
+    // remaining logic deals with formless fields.
+    if (!form.IsNull()) {
+      UpdateLastInteractedForm(form);
+      return;
     }
+    if (!unsafe_render_frame()) {
+      return;
+    }
+    // Remove visible elements.
+    // TODO(crbug.com/1483242): Investigate if this is necessary: if it is,
+    // document the reason, if not, remove.
+    WebDocument doc = unsafe_render_frame()->GetWebFrame()->GetDocument();
+    if (!doc.IsNull()) {
+      std::erase_if(formless_elements_user_edited_,
+                    [&doc](const FieldRendererId field_id) {
+                      WebFormControlElement field =
+                          form_util::FindFormControlByRendererId(
+                              doc, field_id,
+                              /*form_to_be_searched =*/FormRendererId());
+                      return !field.IsNull() &&
+                             form_util::IsWebElementFocusableForAutofill(field);
+                    });
+    }
+    // Update provisionally_saved_form_. Afterwards, only keep one of
+    // `provisionally_saved_form_` or `last_interacted_form_` non-null to avoid
+    // tracking different and outdated elements.
+    formless_elements_user_edited_.insert(
+        form_util::GetFieldRendererId(element));
+    provisionally_saved_form_ = CollectFormlessElements();
+    // TODO(crbug.com/1483242): Investigate why don't we reset
+    // `last_interacted_form_` except when formless extraction fails, document
+    // the reason if any, cleanup otherwise.
+    if (provisionally_saved_form_) {
+      last_interacted_form_.Reset();
+    }
+  };
 
-    if (source == ElementChangeSource::TEXTFIELD_CHANGED) {
+  switch (source) {
+    case FormTracker::Observer::ElementChangeSource::WILL_SEND_SUBMIT_EVENT:
+      // Fire the form submission event to avoid missing submissions where
+      // websites handle the onsubmit event. This also gets the form before
+      // Javascript's submit event handler could change it. We don't clear
+      // submitted_forms_ because OnFormSubmitted will normally be invoked
+      // afterwards and we don't want to fire the same event twice.
+      FireHostSubmitEvents(form, /*known_success=*/false,
+                           SubmissionSource::FORM_SUBMISSION);
+      ResetLastInteractedElements();
+      break;
+    case FormTracker::Observer::ElementChangeSource::TEXTFIELD_CHANGED:
+      update_submission_data_on_user_edit();
       OnTextFieldDidChange(element);
-    } else {
+      break;
+    case FormTracker::Observer::ElementChangeSource::SELECT_CHANGED:
+      update_submission_data_on_user_edit();
+      // Signal the browser of change in select fields.
+      // TODO(crbug.com/1483242): Investigate if this is necessary: if it is,
+      // document the reason, if not, remove.
       FormData form_data;
       FormFieldData field;
-      if (FindFormAndFieldForFormControlElement(
-              element, field_data_manager(),
-              MaybeExtractDatalist({ExtractOption::kBounds}), &form_data,
-              &field)) {
-        if (auto* autofill_driver = unsafe_autofill_driver()) {
-          autofill_driver->SelectControlDidChange(form_data, field,
-                                                  field.bounds);
-        }
+      if (auto* autofill_driver = unsafe_autofill_driver();
+          autofill_driver && FindFormAndFieldForFormControlElement(
+                                 element, field_data_manager(),
+                                 MaybeExtractDatalist({ExtractOption::kBounds}),
+                                 &form_data, &field)) {
+        autofill_driver->SelectControlDidChange(form_data, field, field.bounds);
       }
-    }
+      break;
   }
   SendPotentiallySubmittedFormToBrowser();
 }
 
 void AutofillAgent::OnProbablyFormSubmitted() {
-  absl::optional<FormData> form_data = GetSubmittedForm();
-  if (form_data.has_value()) {
+  if (std::optional<FormData> form_data = GetSubmittedForm()) {
     FireHostSubmitEvents(form_data.value(), /*known_success=*/false,
                          SubmissionSource::PROBABLY_FORM_SUBMITTED);
   }
@@ -1550,21 +1565,32 @@
     return;
   }
   switch (source) {
+    // This source is only used as a default values to variables.
     case mojom::SubmissionSource::NONE:
+    // This source is handled by `AutofillAgent::OnFormSubmitted`.
     case mojom::SubmissionSource::FORM_SUBMISSION:
+    // This source is handled by `AutofillAgent::OnProbablyFormSubmitted`.
     case mojom::SubmissionSource::PROBABLY_FORM_SUBMITTED:
       NOTREACHED_NORETURN();
     case mojom::SubmissionSource::SAME_DOCUMENT_NAVIGATION:
+      // TODO(crbug.com/1483242): Investigate if discarding subframe same
+      // document navigation is necessary: if it is, document the reason, if
+      // not, remove.
       if (!unsafe_render_frame()->GetWebFrame()->IsOutermostMainFrame()) {
         break;
       }
-      if (absl::optional<FormData> form_data = GetSubmittedForm(); form_data) {
-        FireHostSubmitEvents(form_data.value(), /*known_success=*/true, source);
+      if (std::optional<FormData> form_data = GetSubmittedForm()) {
+        FireHostSubmitEvents(*form_data, /*known_success=*/true, source);
       }
       break;
     // This event occurs only when either this frame or a same process parent
     // frame of it gets detached.
     case mojom::SubmissionSource::FRAME_DETACHED:
+      // Detaching the main frame means that navigation happened or the current
+      // tab was closed, both reasons being too general to be able to deduce
+      // submission from it (and the relevant use cases will most probably be
+      // handled by other sources), therefore we only consider detached
+      // subframes.
       if (!unsafe_render_frame()->GetWebFrame()->IsOutermostMainFrame() &&
           provisionally_saved_form_.has_value()) {
         // Should not access the frame because it is now detached. Instead, use
@@ -1575,8 +1601,8 @@
       break;
     case mojom::SubmissionSource::XHR_SUCCEEDED:
     case mojom::SubmissionSource::DOM_MUTATION_AFTER_XHR:
-      if (absl::optional<FormData> form_data = GetSubmittedForm(); form_data) {
-        FireHostSubmitEvents(form_data.value(), /*known_success=*/true, source);
+      if (std::optional<FormData> form_data = GetSubmittedForm()) {
+        FireHostSubmitEvents(*form_data, /*known_success=*/true, source);
       }
       break;
   }
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index 3c29c321..e25c092 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -345,8 +345,9 @@
   void HidePopup();
 
   // Attempt to get submitted FormData from last_interacted_form_ or
-  // provisionally_saved_form_, return true if |form| is set.
-  absl::optional<FormData> GetSubmittedForm() const;
+  // provisionally_saved_form_, return the form in question if found, and
+  // std::nullopt otherwise.
+  std::optional<FormData> GetSubmittedForm() const;
 
   // Pushes the value of GetSubmittedForm() to the AutofillDriver.
   void SendPotentiallySubmittedFormToBrowser();
@@ -406,7 +407,7 @@
   // The form the user interacted with last. It is used if last_interacted_form_
   // or a formless form can't be converted to FormData at the time of form
   // submission (e.g. because they have been removed from the DOM).
-  absl::optional<FormData> provisionally_saved_form_;
+  std::optional<FormData> provisionally_saved_form_;
 
   // Keeps track of the forms for which form submitted event has been sent to
   // AutofillDriver. We use it to avoid fire duplicated submission event when
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index 045f6fd..1972d3d 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -2104,8 +2104,6 @@
   field->value = std::move(value).substr(0, kMaxStringLength);
   field->selected_text =
       element.SelectedText().Utf16().substr(0, kMaxSelectedTextLength);
-  field->selection_start = std::min(element.SelectionStart(), kMaxStringLength);
-  field->selection_end = std::min(element.SelectionEnd(), kMaxStringLength);
 
   // If the field was autofilled or the user typed into it, check the value
   // stored in |field_data_manager| against the value property of the DOM
diff --git a/components/autofill/content/renderer/form_tracker.cc b/components/autofill/content/renderer/form_tracker.cc
index cd8b5cd..6de5741 100644
--- a/components/autofill/content/renderer/form_tracker.cc
+++ b/components/autofill/content/renderer/form_tracker.cc
@@ -210,6 +210,8 @@
     last_interacted_formless_element_ = FieldRef(element);
   else
     last_interacted_form_ = FormRef(element.Form());
+  // TODO(crbug.com/1483242): Investigate if this is necessary: if it is,
+  // document the reason, if not, remove.
   TrackElement();
 }
 
@@ -261,10 +263,6 @@
     return;
   }
 
-  // Bug fix for crbug.com/368690. isProcessingUserGesture() is false when
-  // the user is performing actions outside the page (e.g. typed url,
-  // history navigation). We don't want to trigger saving in these cases.
-
   // We are interested only in content-initiated navigations. Explicit browser
   // initiated navigations (e.g. via omnibox) don't have a navigation type
   // and are discarded here.
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index 6de6ce7..7e18467 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -511,6 +511,26 @@
   }
 }
 
+bool IsAutofillManuallyTriggered(AutofillSuggestionTriggerSource source) {
+  switch (source) {
+    case AutofillSuggestionTriggerSource::kManualFallbackAddress:
+    case AutofillSuggestionTriggerSource::kManualFallbackPayments:
+      return true;
+    case AutofillSuggestionTriggerSource::kUnspecified:
+    case AutofillSuggestionTriggerSource::kFormControlElementClicked:
+    case AutofillSuggestionTriggerSource::kContentEditableClicked:
+    case AutofillSuggestionTriggerSource::kTextFieldDidChange:
+    case AutofillSuggestionTriggerSource::kTextFieldDidReceiveKeyDown:
+    case AutofillSuggestionTriggerSource::kOpenTextDataListChooser:
+    case AutofillSuggestionTriggerSource::kShowCardsFromAccount:
+    case AutofillSuggestionTriggerSource::kPasswordManager:
+    case AutofillSuggestionTriggerSource::kAndroidWebView:
+    case AutofillSuggestionTriggerSource::kiOS:
+    case AutofillSuggestionTriggerSource::kShowPromptAfterDialogClosed:
+      return false;
+  }
+}
+
 // Returns true if autocomplete=unrecognized (address) fields should receive
 // suggestions. On desktop, suggestion can only be triggered for them through
 // manual fallbacks. On mobile, it depends on
@@ -523,8 +543,7 @@
   return base::FeatureList::IsEnabled(
       features::kAutofillSuggestionsForAutocompleteUnrecognizedFieldsOnMobile);
 #else
-  return trigger_source ==
-         AutofillSuggestionTriggerSource::kManualFallbackAddress;
+  return IsAutofillManuallyTriggered(trigger_source);
 #endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
 }
 
@@ -1390,11 +1409,13 @@
     credit_card_form_ = form;
     credit_card_field_ = field;
 
-    // CreditCardAccessManager::FetchCreditCard() will call
+    // CreditCardAccessManager::FetchCreditCard() will trigger
     // OnCreditCardFetched() in this class after successfully fetching the card.
     fetched_credit_card_trigger_source_ = trigger_details.trigger_source;
     credit_card_access_manager_->FetchCreditCard(
-        credit_card, weak_ptr_factory_.GetWeakPtr());
+        credit_card,
+        base::BindOnce(&BrowserAutofillManager::OnCreditCardFetched,
+                       weak_ptr_factory_.GetWeakPtr()));
     return;
   }
 
@@ -2232,15 +2253,11 @@
   if (!IsAutofillEnabled())
     return false;
 
-  // No autofill data to return if the profiles are empty.
-  const std::vector<AutofillProfile*>& profiles =
-      client().GetPersonalDataManager()->GetProfiles();
   credit_card_access_manager_->UpdateCreditCardFormEventLogger();
 
-  // Updating the FormEventLogger for addresses.
-  address_form_event_logger_->set_local_record_type_count(profiles.size());
-  // TODO(crbug.com/1457187): Server addresses don't exist anymore. Remove.
-  address_form_event_logger_->set_server_record_type_count(0);
+  const std::vector<AutofillProfile*>& profiles =
+      client().GetPersonalDataManager()->GetProfiles();
+  address_form_event_logger_->set_record_type_count(profiles.size());
 
   return !profiles.empty() ||
          !client().GetPersonalDataManager()->GetCreditCards().empty();
@@ -3490,15 +3507,17 @@
 
   // Log interactions of forms that are autofillable.
   if (got_autofillable_form) {
-    if (context->focused_field->Type().group() == FieldTypeGroup::kCreditCard) {
-      context->is_filling_credit_card = true;
-    }
     auto* logger = GetEventFormLogger(*context->focused_field);
     if (logger) {
       logger->OnDidInteractWithAutofillableForm(*(context->form_structure),
                                                 signin_state_for_metrics_);
     }
   }
+  context->is_filling_credit_card =
+      trigger_source ==
+          AutofillSuggestionTriggerSource::kManualFallbackPayments ||
+      (got_autofillable_form &&
+       context->focused_field->Type().group() == FieldTypeGroup::kCreditCard);
 
   // If the feature is enabled and this is a mixed content form, we show a
   // warning message and don't offer autofill. The warning is shown even if
@@ -3520,20 +3539,22 @@
     }
     return;
   }
-
   context->is_context_secure = !IsFormNonSecure(form);
 
-  if (!got_autofillable_form || !IsAutofillEnabled()) {
+  context->is_autofill_available =
+      IsAutofillEnabled() &&
+      (IsAutofillManuallyTriggered(trigger_source) || got_autofillable_form);
+  if (!context->is_autofill_available) {
     return;
   }
 
-  context->is_autofill_available = true;
-
   if (context->is_filling_credit_card) {
-    // Credit cards suggestions don't depend the `trigger_source`.
-    *suggestions = GetCreditCardSuggestions(
-        field, context->focused_field->Type().GetStorableType(),
-        context->should_display_gpay_logo);
+    ServerFieldType trigger_field_type =
+        context->focused_field
+            ? context->focused_field->Type().GetStorableType()
+            : UNKNOWN_TYPE;
+    *suggestions = GetCreditCardSuggestions(field, trigger_field_type,
+                                            context->should_display_gpay_logo);
   } else {
     // Profile suggestions fill ac=unrecognized fields only when triggered
     // through manual fallbacks. As such, suggestion labels differ depending on
diff --git a/components/autofill/core/browser/browser_autofill_manager.h b/components/autofill/core/browser/browser_autofill_manager.h
index 3ff9ef5e..30d5593 100644
--- a/components/autofill/core/browser/browser_autofill_manager.h
+++ b/components/autofill/core/browser/browser_autofill_manager.h
@@ -100,9 +100,9 @@
 
 // Manages saving and restoring the user's personal information entered into web
 // forms. One per frame; owned by the AutofillDriver.
-class BrowserAutofillManager : public AutofillManager,
-                               public SingleFieldFormFiller::SuggestionsHandler,
-                               public CreditCardAccessManager::Accessor {
+class BrowserAutofillManager
+    : public AutofillManager,
+      public SingleFieldFormFiller::SuggestionsHandler {
  public:
   BrowserAutofillManager(AutofillDriver* driver,
                          AutofillClient* client,
@@ -470,9 +470,11 @@
       bool skip_unrecognized_autocomplete_fields,
       bool is_refill) const;
 
-  // CreditCardAccessManager::Accessor
+  // When `FillOrPreviewCreditCardForm()` fetches a credit card, this gets
+  // called once the fetching has finished. If successful, the `credit_card` is
+  // filled.
   void OnCreditCardFetched(CreditCardFetchResult result,
-                           const CreditCard* credit_card) override;
+                           const CreditCard* credit_card);
 
   // Returns false if Autofill is disabled or if no Autofill data is available.
   bool RefreshDataModels();
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
index 9159b7d..023de04 100644
--- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -1598,7 +1598,38 @@
 }
 #else
 TEST_F(BrowserAutofillManagerTest,
-       GetProfileSuggestions_UnrecognizedAttribute_Predictions_Desktop) {
+       AutofillManualFallback_UnclassifiedField_SuggestionsShown) {
+  base::test::ScopedFeatureList enabled_features(
+      features::kAutofillPredictionsForAutocompleteUnrecognized);
+
+  // Create a form where the first field is unclassifiable.
+  FormData form = CreateTestAddressFormData();
+  form.fields[0].label = u"unclassified";
+  form.fields[0].name = u"unclassified";
+  FormsSeen({form});
+
+  // Expect that no suggestions are returned for the first field.
+  const FormFieldData& first_field = form.fields[0];
+  GetAutofillSuggestions(form, first_field);
+  external_delegate()->CheckSuggestionsNotReturned(first_field.global_id());
+
+  // Expect that no suggestions are returned because the field is unclassified.
+  // TODO(crbug.com/1493361): Revisit when address suggestions are generated for
+  // unclassified fields.
+  GetAutofillSuggestions(
+      form, first_field,
+      AutofillSuggestionTriggerSource::kManualFallbackAddress);
+  external_delegate()->CheckSuggestionsNotReturned(first_field.global_id());
+  // Expect 3 credit card suggestions because the fixture created 3 credit cards
+  // during setup (see `CreateTestCreditCards()`).
+  GetAutofillSuggestions(
+      form, first_field,
+      AutofillSuggestionTriggerSource::kManualFallbackPayments);
+  external_delegate()->CheckSuggestionCount(first_field.global_id(), 3);
+}
+
+TEST_F(BrowserAutofillManagerTest,
+       AutofillManualFallback_AutocompleteUnrecognized_SuggestionsShown) {
   base::test::ScopedFeatureList enabled_features(
       features::kAutofillPredictionsForAutocompleteUnrecognized);
 
@@ -1609,17 +1640,23 @@
   FormsSeen({form});
 
   // Expect that no suggestions are returned for the first field by default.
-  const FormFieldData& field0 = form.fields[0];
-  GetAutofillSuggestions(form, field0);
-  external_delegate()->CheckNoSuggestions(field0.global_id());
+  const FormFieldData& first_field = form.fields[0];
+  GetAutofillSuggestions(form, first_field);
+  external_delegate()->CheckNoSuggestions(first_field.global_id());
 
-  // When triggering suggestions through manual fallbacks, expect that two
-  // suggestions are returned.
-  // Two, because the fixture created three profiles during set up, one of which
-  // is empty and cannot be suggested (see `CreateTestAutofillProfiles()`).
+  // Expect 2 suggestions because the fixture created three profiles during set
+  // up, one of which is empty and cannot be suggested
+  // (see `CreateTestAutofillProfiles()`).
   GetAutofillSuggestions(
-      form, field0, AutofillSuggestionTriggerSource::kManualFallbackAddress);
-  external_delegate()->CheckSuggestionCount(field0.global_id(), 2);
+      form, first_field,
+      AutofillSuggestionTriggerSource::kManualFallbackAddress);
+  external_delegate()->CheckSuggestionCount(first_field.global_id(), 2);
+  // Expect 3 credit card suggestions because the fixture created 3 credit cards
+  // during setup (see `CreateTestCreditCards()`).
+  GetAutofillSuggestions(
+      form, first_field,
+      AutofillSuggestionTriggerSource::kManualFallbackPayments);
+  external_delegate()->CheckSuggestionCount(first_field.global_id(), 3);
 
   // Expect that two suggestions are returned for all other fields.
   for (size_t i = 1; i < form.fields.size(); i++) {
@@ -1627,6 +1664,64 @@
     external_delegate()->CheckSuggestionCount(form.fields[i].global_id(), 2);
   }
 }
+
+TEST_F(BrowserAutofillManagerTest,
+       AutofillManualFallback_ClassifiedField_AddressForm_ShowSuggestions) {
+  base::test::ScopedFeatureList enabled_features(
+      features::kAutofillPredictionsForAutocompleteUnrecognized);
+
+  // Create a form where all fields can be classified.
+  FormData form = CreateTestAddressFormData();
+  FormsSeen({form});
+
+  for (const auto& field : form.fields) {
+    // Expect 2 suggestions because the fixture created three profiles during
+    // set up, one of which is empty and cannot be suggested
+    // (see `CreateTestAutofillProfiles()`).
+    GetAutofillSuggestions(
+        form, field, AutofillSuggestionTriggerSource::kManualFallbackAddress);
+    external_delegate()->CheckSuggestionCount(field.global_id(), 2);
+    base::ranges::all_of(
+        external_delegate()->suggestions(), [](const Suggestion& suggestion) {
+          return suggestion.popup_item_id == PopupItemId::kAddressEntry;
+        });
+    // Expect 3 credit card suggestions because the fixture created 3 credit
+    // cards during setup (see `CreateTestCreditCards()`).
+    GetAutofillSuggestions(
+        form, field, AutofillSuggestionTriggerSource::kManualFallbackPayments);
+    external_delegate()->CheckSuggestionCount(field.global_id(), 3);
+    base::ranges::all_of(
+        external_delegate()->suggestions(), [](const Suggestion& suggestion) {
+          return suggestion.popup_item_id == PopupItemId::kEntryNotSelectable;
+        });
+  }
+}
+
+TEST_F(BrowserAutofillManagerTest,
+       AutofillManualFallback_ClassifiedField_PaymentsForm_ShowSuggestions) {
+  base::test::ScopedFeatureList enabled_features(
+      features::kAutofillPredictionsForAutocompleteUnrecognized);
+
+  // Create a form where all fields can be classified.
+  FormData form =
+      CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
+  FormsSeen({form});
+
+  // TODO(crbug.com/1493361): add expectation on the address manual fallback
+  // suggestions shown after the address suggestions are generated for
+  // unclassified fields.
+  const FormFieldData& cc_name_field = form.fields[0];
+  // Expect 2 suggestions because manual fallback flow triggered on a classified
+  // credit card field should generate regular suggestions.
+  GetAutofillSuggestions(
+      form, cc_name_field,
+      AutofillSuggestionTriggerSource::kManualFallbackPayments);
+  external_delegate()->CheckSuggestionCount(cc_name_field.global_id(), 2);
+  base::ranges::all_of(
+      external_delegate()->suggestions(), [](const Suggestion& suggestion) {
+        return suggestion.popup_item_id == PopupItemId::kCreditCardEntry;
+      });
+}
 #endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
 
 // Test that when small forms are disabled (min required fields enforced) no
diff --git a/components/autofill/core/browser/form_data_importer.h b/components/autofill/core/browser/form_data_importer.h
index 5a67390e..9d0dcea3 100644
--- a/components/autofill/core/browser/form_data_importer.h
+++ b/components/autofill/core/browser/form_data_importer.h
@@ -415,7 +415,6 @@
 
   friend class AutofillMergeTest;
   friend class FormDataImporterTest;
-  friend class FormDataImporterTestBase;
   friend class LocalCardMigrationBrowserTest;
   friend class SaveCardBubbleViewsFullFormBrowserTest;
   friend class SaveCardInfobarEGTestHelper;
diff --git a/components/autofill/core/browser/form_data_importer_unittest.cc b/components/autofill/core/browser/form_data_importer_unittest.cc
index 8fd2360d..f038cf3 100644
--- a/components/autofill/core/browser/form_data_importer_unittest.cc
+++ b/components/autofill/core/browser/form_data_importer_unittest.cc
@@ -513,16 +513,28 @@
               (override));
 };
 
-class FormDataImporterTestBase {
+class FormDataImporterTest : public testing::Test {
  public:
   using ExtractedFormData = FormDataImporter::ExtractedFormData;
   using AddressProfileImportCandidate =
       FormDataImporter::AddressProfileImportCandidate;
 
  protected:
-  FormDataImporterTestBase() = default;
+  FormDataImporterTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {features::kAutofillUseI18nAddressModel,
+         features::kAutofillEnableSupportForApartmentNumbers,
+         features::kAutofillEnableSupportForLandmark,
+         features::kAutofillEnableSupportForBetweenStreets,
+         features::kAutofillEnableSupportForAdminLevel2,
+         features::kAutofillEnableSupportForAddressOverflow,
+         features::kAutofillEnableSupportForBetweenStreetsOrLandmark,
+         features::kAutofillEnableSupportForAddressOverflowAndLandmark,
+         features::kAutofillEnableParsingOfStreetLocation},
+        {});
+  }
 
-  void SetUpHelper() {
+  void SetUp() override {
     prefs_ = test::PrefServiceForTesting();
 
     autofill_client_ = std::make_unique<TestAutofillClient>();
@@ -562,7 +574,7 @@
         std::move(credit_card_save_manager);
   }
 
-  void TearDownHelper() {
+  void TearDown() override {
     if (personal_data_manager_) {
       personal_data_manager_->Shutdown();
     }
@@ -758,36 +770,6 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-class FormDataImporterTest : public FormDataImporterTestBase,
-                             public testing::Test {
- public:
-  using ExtractedFormData = FormDataImporter::ExtractedFormData;
-
- private:
-  void SetUp() override {
-    InitializeFeatures();
-    SetUpHelper();
-  }
-
-  void TearDown() override { TearDownHelper(); }
-
-  void InitializeFeatures() {
-    scoped_feature_list_.InitWithFeatures(
-        {
-            features::kAutofillUseI18nAddressModel,
-            features::kAutofillEnableSupportForApartmentNumbers,
-            features::kAutofillEnableSupportForLandmark,
-            features::kAutofillEnableSupportForBetweenStreets,
-            features::kAutofillEnableSupportForAdminLevel2,
-            features::kAutofillEnableSupportForAddressOverflow,
-            features::kAutofillEnableSupportForBetweenStreetsOrLandmark,
-            features::kAutofillEnableSupportForAddressOverflowAndLandmark,
-            features::kAutofillEnableParsingOfStreetLocation,
-        },
-        {});
-  }
-};
-
 // Tests that the country is not complemented if a country is part of the form.
 TEST_F(FormDataImporterTest, ComplementCountry_PartOfForm) {
   AutofillProfile kDefaultGermanProfile =
@@ -3078,10 +3060,10 @@
 TEST_F(FormDataImporterTest, ExtractFormData_ImportIbanRecordType_LocalIban) {
   Iban iban;
   iban.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue)));
-  const std::string guid = personal_data_manager_->AddIban(iban);
+  const std::string guid = personal_data_manager_->AddAsLocalIban(iban);
   // Should set identifier and record_type manually here as `iban` has been
-  // passed by value above in `AddIban`, and `AddIban` method sets identifier
-  // and record_type to the given `iban`.
+  // passed by value above in `AddAsLocalIban`, and `AddAsLocalIban` method sets
+  // identifier and record_type to the given `iban`.
   iban.set_identifier(Iban::Guid(guid));
   iban.set_record_type(Iban::kLocalIban);
 
@@ -3969,7 +3951,7 @@
        ExtractFormData_ProcessIbanImportCandidate_LocalIban) {
   Iban iban;
   iban.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue)));
-  personal_data_manager_->AddIban(iban);
+  personal_data_manager_->AddAsLocalIban(iban);
 
   // Simulate a form submission with the same IBAN. The IBAN should not be
   // offered to be saved, because it already exists as a local IBAN.
@@ -4004,15 +3986,7 @@
 }
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
 
-class FormDataImporterNonParameterizedTest : public FormDataImporterTestBase,
-                                             public testing::Test {
- private:
-  void SetUp() override { SetUpHelper(); }
-  void TearDown() override { TearDownHelper(); }
-};
-
-TEST_F(FormDataImporterNonParameterizedTest,
-       ProcessExtractedCreditCard_EmptyCreditCard) {
+TEST_F(FormDataImporterTest, ProcessExtractedCreditCard_EmptyCreditCard) {
   absl::optional<CreditCard> extracted_credit_card;
   std::unique_ptr<FormStructure> form_structure =
       ConstructDefaultCreditCardFormStructure();
@@ -4037,8 +4011,7 @@
 }
 
 #if !BUILDFLAG(IS_IOS)
-TEST_F(FormDataImporterNonParameterizedTest,
-       ProcessExtractedCreditCard_VirtualCardEligible) {
+TEST_F(FormDataImporterTest, ProcessExtractedCreditCard_VirtualCardEligible) {
   CreditCard extracted_credit_card = test::GetMaskedServerCard();
   extracted_credit_card.SetNetworkForMaskedCard(kAmericanExpressCard);
   extracted_credit_card.set_instrument_id(1111);
@@ -4085,7 +4058,7 @@
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
 // Test that in the case where the MandatoryReauthManager denotes we should not
 // offer re-auth opt-in, we do not start the opt-in flow.
-TEST_F(FormDataImporterNonParameterizedTest,
+TEST_F(FormDataImporterTest,
        ProcessExtractedCreditCard_MandatoryReauthNotOffered) {
   CreditCard extracted_credit_card = test::GetVirtualCard();
   std::unique_ptr<FormStructure> form_structure =
@@ -4116,7 +4089,7 @@
 
 // Test that in the case where the MandatoryReauthManager denotes we should
 // offer re-auth opt-in, we start the opt-in flow.
-TEST_F(FormDataImporterNonParameterizedTest,
+TEST_F(FormDataImporterTest,
        ProcessExtractedCreditCard_MandatoryReauthOffered) {
   CreditCard extracted_credit_card = test::GetCreditCard2();
   std::unique_ptr<FormStructure> form_structure =
@@ -4154,7 +4127,7 @@
 
 // Test that ProceedWithSavingIfApplicable gets called for server cards with the
 // correct pre-requisites set.
-TEST_F(FormDataImporterNonParameterizedTest,
+TEST_F(FormDataImporterTest,
        ProcessExtractedCreditCard_ProceedWithSavingIfApplicable_Server) {
   CreditCard card = test::WithCvc(test::GetCreditCard(), u"123");
   std::unique_ptr<FormStructure> form_structure =
@@ -4171,7 +4144,7 @@
 
 // Test that ProceedWithSavingIfApplicable gets called for local cards with the
 // correct pre-requisites set.
-TEST_F(FormDataImporterNonParameterizedTest,
+TEST_F(FormDataImporterTest,
        ProcessExtractedCreditCard_ProceedWithSavingIfApplicable_Local) {
   CreditCard card = test::WithCvc(test::GetCreditCard(), u"123");
   std::unique_ptr<FormStructure> form_structure =
diff --git a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
index 75d9a94..d4faf72 100644
--- a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
+++ b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
@@ -4567,7 +4567,7 @@
 
   base::HistogramTester histogram_tester;
   SeeForm(form);
-  histogram_tester.ExpectUniqueSample("Autofill.FormEvents.Address.WithNoData",
+  histogram_tester.ExpectUniqueSample("Autofill.FormEvents.Address",
                                       FORM_EVENT_DID_PARSE_FORM, 1);
   histogram_tester.ExpectUniqueSample(
       "Autofill.FormEvents.CreditCard.WithNoData", FORM_EVENT_DID_PARSE_FORM,
@@ -4587,7 +4587,7 @@
 
   base::HistogramTester histogram_tester;
   SeeForm(form);
-  histogram_tester.ExpectUniqueSample("Autofill.FormEvents.Address.WithNoData",
+  histogram_tester.ExpectUniqueSample("Autofill.FormEvents.Address",
                                       FORM_EVENT_DID_PARSE_FORM, 1);
 
   // Check if FormEvent UKM is logged properly
@@ -5199,44 +5199,6 @@
   }
 }
 
-// Test that we log interacted form event for address only once.
-TEST_F(AutofillMetricsTest, AddressFormEventsAreSegmented) {
-  FormData form = CreateForm(
-      {CreateTestFormField("State", "state", "", FormControlType::kInputText),
-       CreateTestFormField("City", "city", "", FormControlType::kInputText),
-       CreateTestFormField("Street", "street", "",
-                           FormControlType::kInputText)});
-
-  std::vector<ServerFieldType> field_types = {
-      ADDRESS_HOME_STATE, ADDRESS_HOME_CITY, ADDRESS_HOME_STREET_ADDRESS};
-
-  autofill_manager().AddSeenForm(form, field_types);
-  personal_data().ClearProfiles();
-
-  {
-    // Simulate activating the autofill popup for the street field.
-    base::HistogramTester histogram_tester;
-    autofill_manager().OnAskForValuesToFillTest(form, form.fields[2]);
-    histogram_tester.ExpectUniqueSample(
-        "Autofill.FormEvents.Address.WithNoData", FORM_EVENT_INTERACTED_ONCE,
-        1);
-  }
-
-  // Reset the autofill manager state.
-  autofill_manager().Reset();
-  autofill_manager().AddSeenForm(form, field_types);
-  RecreateProfile();
-
-  {
-    // Simulate activating the autofill popup for the street field.
-    base::HistogramTester histogram_tester;
-    autofill_manager().OnAskForValuesToFillTest(form, form.fields[2]);
-    histogram_tester.ExpectUniqueSample(
-        "Autofill.FormEvents.Address.WithOnlyLocalData",
-        FORM_EVENT_INTERACTED_ONCE, 1);
-  }
-}
-
 // Test that we log that Profile Autofill is enabled when filling a form.
 TEST_F(AutofillMetricsTest, AutofillProfileIsEnabledAtPageLoad) {
   base::HistogramTester histogram_tester;
diff --git a/components/autofill/core/browser/metrics/form_events/address_form_event_logger.cc b/components/autofill/core/browser/metrics/form_events/address_form_event_logger.cc
index a019be65..dd5a41ff 100644
--- a/components/autofill/core/browser/metrics/form_events/address_form_event_logger.cc
+++ b/components/autofill/core/browser/metrics/form_events/address_form_event_logger.cc
@@ -146,4 +146,16 @@
                             !has_logged_edited_autofilled_field_);
 }
 
+void AddressFormEventLogger::LogUkmInteractedWithForm(
+    FormSignature form_signature) {
+  // Address Autofill has deprecated the concept of server addresses.
+  form_interactions_ukm_logger_->LogInteractedWithForm(
+      /*is_for_credit_card=*/false, record_type_count_,
+      /*server_record_type_count=*/0, form_signature);
+}
+
+bool AddressFormEventLogger::HasLoggedDataToFillAvailable() const {
+  return record_type_count_ > 0;
+}
+
 }  // namespace autofill::autofill_metrics
diff --git a/components/autofill/core/browser/metrics/form_events/address_form_event_logger.h b/components/autofill/core/browser/metrics/form_events/address_form_event_logger.h
index 0f567e7..8559625d 100644
--- a/components/autofill/core/browser/metrics/form_events/address_form_event_logger.h
+++ b/components/autofill/core/browser/metrics/form_events/address_form_event_logger.h
@@ -41,6 +41,10 @@
 
   ~AddressFormEventLogger() override;
 
+  void set_record_type_count(size_t record_type_count) {
+    record_type_count_ = record_type_count;
+  }
+
   void OnDidFillSuggestion(
       const AutofillProfile& profile,
       const FormStructure& form,
@@ -72,10 +76,16 @@
   void RecordFillingAssistance(LogBuffer& logs) const override;
   void RecordFillingCorrectness(LogBuffer& logs) const override;
 
+  void LogUkmInteractedWithForm(FormSignature form_signature) override;
+
+  bool HasLoggedDataToFillAvailable() const override;
+
  private:
   // All profile categories for which the user has accepted at least one
   // suggestion - used for metrics.
   DenseSet<AutofillProfileSourceCategory> profile_categories_filled_;
+
+  size_t record_type_count_ = 0;
 };
 
 }  // namespace autofill::autofill_metrics
diff --git a/components/autofill/core/browser/metrics/form_events/address_form_event_logger_unittest.cc b/components/autofill/core/browser/metrics/form_events/address_form_event_logger_unittest.cc
index d24203a..ee1f4a7 100644
--- a/components/autofill/core/browser/metrics/form_events/address_form_event_logger_unittest.cc
+++ b/components/autofill/core/browser/metrics/form_events/address_form_event_logger_unittest.cc
@@ -15,48 +15,6 @@
 
 namespace autofill::autofill_metrics {
 
-class AddressFormEventLoggerTest : public AutofillMetricsBaseTest,
-                                   public testing::Test {
- public:
-  void SetUp() override { SetUpHelper(); }
-  void TearDown() override { TearDownHelper(); }
-};
-
-// Verify that FormEvent metrics log the appropriate sync state.
-TEST_F(AddressFormEventLoggerTest, SyncState) {
-  FormData form;
-  FormStructure form_structure(form);
-  SeeForm(form);
-  autofill_manager().Reset();
-
-  {
-    base::HistogramTester histogram_tester;
-    AddressFormEventLogger logger(
-        /*is_in_any_main_frame=*/true,
-        /*form_interactions_ukm_logger=*/nullptr,
-        /*client=*/autofill_client_.get());
-    logger.OnDidSeeFillableDynamicForm(
-        AutofillMetrics::PaymentsSigninState::kSignedOut, form_structure);
-    histogram_tester.ExpectBucketCount(
-        "Autofill.FormEvents.Address.WithNoData.SignedOut",
-        FORM_EVENT_DID_SEE_FILLABLE_DYNAMIC_FORM, 1);
-    logger.OnDestroyed();
-  }
-  {
-    base::HistogramTester histogram_tester;
-    AddressFormEventLogger logger(
-        /*is_in_any_main_frame=*/true,
-        /*form_interactions_ukm_logger=*/nullptr,
-        /*client=*/autofill_client_.get());
-    logger.OnDidRefill(AutofillMetrics::PaymentsSigninState::kSignedIn,
-                       form_structure);
-    histogram_tester.ExpectBucketCount(
-        "Autofill.FormEvents.Address.WithNoData.SignedIn",
-        FORM_EVENT_DID_DYNAMIC_REFILL, 1);
-    logger.OnDestroyed();
-  }
-}
-
 class CategoryResolvedKeyMetricsTest
     : public autofill_metrics::AutofillMetricsBaseTest,
       public testing::Test {
diff --git a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
index ba1644bbc..13834df2 100644
--- a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
+++ b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
@@ -304,6 +304,26 @@
   UpdateFlowId();
 }
 
+void CreditCardFormEventLogger::Log(FormEvent event,
+                                    const FormStructure& form) {
+  FormEventLoggerBase::Log(event, form);
+  std::string name = "Autofill.FormEvents.CreditCard";
+  if (server_record_type_count_ == 0 && local_record_type_count_ == 0) {
+    name += ".WithNoData";
+  } else if (server_record_type_count_ > 0 && local_record_type_count_ == 0) {
+    name += ".WithOnlyServerData";
+  } else if (server_record_type_count_ == 0 && local_record_type_count_ > 0) {
+    name += ".WithOnlyLocalData";
+  } else {
+    name += ".WithBothServerAndLocalData";
+  }
+  base::UmaHistogramEnumeration(name, event, NUM_FORM_EVENTS);
+  base::UmaHistogramEnumeration(
+      name +
+          AutofillMetrics::GetMetricsSyncStateSuffix(signin_state_for_metrics_),
+      event, NUM_FORM_EVENTS);
+}
+
 void CreditCardFormEventLogger::LogCardUnmaskAuthenticationPromptShown(
     UnmaskAuthFlowType flow) {
   RecordCardUnmaskFlowEvent(flow, UnmaskAuthFlowEvent::kPromptShown);
@@ -461,6 +481,10 @@
   }
 }
 
+bool CreditCardFormEventLogger::HasLoggedDataToFillAvailable() const {
+  return server_record_type_count_ + local_record_type_count_ > 0;
+}
+
 FormEvent CreditCardFormEventLogger::GetCardNumberStatusFormEvent(
     const CreditCard& credit_card) {
   const std::u16string number = credit_card.number();
diff --git a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h
index efb7e55df..5041956 100644
--- a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h
+++ b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h
@@ -47,6 +47,14 @@
 
   ~CreditCardFormEventLogger() override;
 
+  void set_server_record_type_count(size_t server_record_type_count) {
+    server_record_type_count_ = server_record_type_count;
+  }
+
+  void set_local_record_type_count(size_t local_record_type_count) {
+    local_record_type_count_ = local_record_type_count;
+  }
+
   // Invoked when `suggestions` are successfully fetched. `with_offer` indicates
   // whether an offer is attached to any of the suggestion in the list.
   // `is_virtual_card_standalone_cvc_field` indicates whether the `suggestions`
@@ -98,6 +106,8 @@
       AutofillMetrics::PaymentsSigninState signin_state_for_metrics,
       const AutofillTriggerSource trigger_source);
 
+  void Log(FormEvent event, const FormStructure& form) override;
+
   // Logging what type of authentication flow was prompted.
   void LogCardUnmaskAuthenticationPromptShown(UnmaskAuthFlowType flow);
 
@@ -127,6 +137,7 @@
   void OnLog(const std::string& name,
              FormEvent event,
              const FormStructure& form) const override;
+  bool HasLoggedDataToFillAvailable() const override;
 
   // Bringing base class' Log function into scope to allow overloading.
   using FormEventLoggerBase::Log;
@@ -139,6 +150,8 @@
   // Returns whether the shown suggestions included a virtual credit card.
   bool DoSuggestionsIncludeVirtualCard();
 
+  size_t server_record_type_count_ = 0;
+  size_t local_record_type_count_ = 0;
   UnmaskAuthFlowType current_authentication_flow_;
   bool has_logged_suggestion_with_metadata_shown_ = false;
   bool has_logged_suggestion_with_metadata_selected_ = false;
diff --git a/components/autofill/core/browser/metrics/form_events/form_event_logger_base.cc b/components/autofill/core/browser/metrics/form_events/form_event_logger_base.cc
index 7614387..a6d3798 100644
--- a/components/autofill/core/browser/metrics/form_events/form_event_logger_base.cc
+++ b/components/autofill/core/browser/metrics/form_events/form_event_logger_base.cc
@@ -259,21 +259,6 @@
 
   // Allow specialized types of logging, e.g. splitting metrics in useful ways.
   OnLog(name, event, form);
-
-  // Logging again in a different histogram for segmentation purposes.
-  if (server_record_type_count_ == 0 && local_record_type_count_ == 0)
-    name += ".WithNoData";
-  else if (server_record_type_count_ > 0 && local_record_type_count_ == 0)
-    name += ".WithOnlyServerData";
-  else if (server_record_type_count_ == 0 && local_record_type_count_ > 0)
-    name += ".WithOnlyLocalData";
-  else
-    name += ".WithBothServerAndLocalData";
-  base::UmaHistogramEnumeration(name, event, NUM_FORM_EVENTS);
-  base::UmaHistogramEnumeration(
-      name +
-          AutofillMetrics::GetMetricsSyncStateSuffix(signin_state_for_metrics_),
-      event, NUM_FORM_EVENTS);
 }
 
 void FormEventLoggerBase::LogWillSubmitForm(const FormStructure& form) {
@@ -296,13 +281,6 @@
   }
 }
 
-void FormEventLoggerBase::LogUkmInteractedWithForm(
-    FormSignature form_signature) {
-  form_interactions_ukm_logger_->LogInteractedWithForm(
-      /*is_for_credit_card=*/false, local_record_type_count_,
-      server_record_type_count_, form_signature);
-}
-
 void FormEventLoggerBase::RecordFunnelMetrics() const {
   UmaHistogramBoolean("Autofill.Funnel.ParsedAsType." + form_type_name_,
                       has_parsed_form_);
@@ -381,10 +359,8 @@
     }
     RecordFillingAssistance(logs);
     if (form_interactions_ukm_logger_) {
-      bool has_logged_data_to_fill_available =
-          server_record_type_count_ + local_record_type_count_ > 0;
       form_interactions_ukm_logger_->LogKeyMetrics(
-          submitted_form_types_, has_logged_data_to_fill_available,
+          submitted_form_types_, HasLoggedDataToFillAvailable(),
           has_logged_suggestions_shown_, has_logged_edited_autofilled_field_,
           has_logged_suggestion_filled_, form_interaction_counts_, flow_id_,
           fast_checkout_run_id_);
@@ -401,8 +377,7 @@
 }
 
 void FormEventLoggerBase::RecordFillingReadiness(LogBuffer& logs) const {
-  bool has_logged_data_to_fill_available =
-      server_record_type_count_ + local_record_type_count_ > 0;
+  bool has_logged_data_to_fill_available = HasLoggedDataToFillAvailable();
   UmaHistogramBoolean("Autofill.KeyMetrics.FillingReadiness." + form_type_name_,
                       has_logged_data_to_fill_available);
   LOG_AF(logs) << Tr{} << "FillingReadiness"
diff --git a/components/autofill/core/browser/metrics/form_events/form_event_logger_base.h b/components/autofill/core/browser/metrics/form_events/form_event_logger_base.h
index a674059..15916a9d 100644
--- a/components/autofill/core/browser/metrics/form_events/form_event_logger_base.h
+++ b/components/autofill/core/browser/metrics/form_events/form_event_logger_base.h
@@ -29,14 +29,6 @@
       AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger,
       AutofillClient* client);
 
-  inline void set_server_record_type_count(size_t server_record_type_count) {
-    server_record_type_count_ = server_record_type_count;
-  }
-
-  inline void set_local_record_type_count(size_t local_record_type_count) {
-    local_record_type_count_ = local_record_type_count;
-  }
-
   void OnDidInteractWithAutofillableForm(
       const FormStructure& form,
       AutofillMetrics::PaymentsSigninState signin_state_for_metrics);
@@ -84,7 +76,7 @@
   void OnAutofilledFieldWasClearedByJavaScriptShortlyAfterFill(
       const FormStructure& form);
 
-  void Log(FormEvent event, const FormStructure& form);
+  virtual void Log(FormEvent event, const FormStructure& form);
 
   void OnTextFieldDidChange(const FieldGlobalId& field_global_id);
 
@@ -117,7 +109,7 @@
   // Only used for UKM backward compatibility since it depends on IsCreditCard.
   // TODO (crbug.com/925913): Remove IsCreditCard from UKM logs amd replace with
   // |form_type_name_|.
-  virtual void LogUkmInteractedWithForm(FormSignature form_signature);
+  virtual void LogUkmInteractedWithForm(FormSignature form_signature) = 0;
 
   virtual void OnSuggestionsShownOnce(const FormStructure& form) {}
   virtual void OnSuggestionsShownSubmittedOnce(const FormStructure& form) {}
@@ -171,13 +163,15 @@
 
   void UpdateFlowId();
 
+  // Returns whether the logger was notified that any data to fill is available.
+  // This is used to emit the readiness key metric.
+  virtual bool HasLoggedDataToFillAvailable() const = 0;
+
   // Constructor parameters.
   std::string form_type_name_;
   bool is_in_any_main_frame_;
 
   // State variables.
-  size_t server_record_type_count_ = 0;
-  size_t local_record_type_count_ = 0;
   bool has_parsed_form_ = false;
   bool has_logged_interacted_ = false;
   bool has_logged_user_hide_suggestions_ = false;
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.cc b/components/autofill/core/browser/payments/credit_card_access_manager.cc
index aa4c64e..e40f9746 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.cc
@@ -263,7 +263,7 @@
 
 void CreditCardAccessManager::FetchCreditCard(
     const CreditCard* card,
-    base::WeakPtr<Accessor> accessor) {
+    OnCreditCardFetchedCallback on_credit_card_fetched) {
   // Reset the variable in FormDataImporter that denotes if non-interactive
   // authentication happened. This variable will be set to a value if a payments
   // autofill non-interactive flow successfully completes.
@@ -274,15 +274,15 @@
   // Return error if authentication is already in progress, but don't reset
   // status.
   if (is_authentication_in_progress_) {
-    accessor->OnCreditCardFetched(CreditCardFetchResult::kTransientError,
-                                  nullptr);
+    std::move(on_credit_card_fetched)
+        .Run(CreditCardFetchResult::kTransientError, nullptr);
     return;
   }
 
   // If card is nullptr we reset all states and return error.
   if (!card) {
-    accessor->OnCreditCardFetched(CreditCardFetchResult::kTransientError,
-                                  nullptr);
+    std::move(on_credit_card_fetched)
+        .Run(CreditCardFetchResult::kTransientError, nullptr);
     Reset();
     return;
   }
@@ -304,8 +304,9 @@
       unmasked_card_cache_.find(GetKeyForUnmaskedCardsCache(*card));
   if (it != unmasked_card_cache_.end()) {  // key is in cache
     it->second.card.set_cvc(it->second.cvc);
-    accessor->OnCreditCardFetched(CreditCardFetchResult::kSuccess,
-                                  /*credit_card=*/&it->second.card);
+    std::move(on_credit_card_fetched)
+        .Run(CreditCardFetchResult::kSuccess,
+             /*credit_card=*/&it->second.card);
     std::string metrics_name =
         record_type == CreditCard::RecordType::kVirtualCard
             ? "Autofill.UsedCachedVirtualCard"
@@ -323,7 +324,7 @@
   }
 
   card_ = std::make_unique<CreditCard>(*card);
-  accessor_ = accessor;
+  on_credit_card_fetched_callback_ = std::move(on_credit_card_fetched);
 
   switch (record_type) {
     case CreditCard::RecordType::kVirtualCard:
@@ -425,8 +426,8 @@
   std::vector<CardUnmaskChallengeOption>& challenge_options =
       virtual_card_unmask_response_details_.card_unmask_challenge_options;
   if (challenge_options.empty()) {
-    accessor_->OnCreditCardFetched(CreditCardFetchResult::kTransientError,
-                                   nullptr);
+    std::move(on_credit_card_fetched_callback_)
+        .Run(CreditCardFetchResult::kTransientError, nullptr);
     client_->ShowAutofillErrorDialog(
         AutofillErrorDialogContext::WithVirtualCardPermanentOrTemporaryError(
             /*is_permanent_error=*/true));
@@ -611,7 +612,7 @@
   // contains CVC. `response.card` can be nullptr in the case of an error in the
   // response. If the response has an error, the `ShouldRespondImmediately()`
   // call below will return true and we will safely pass nullptr and that it is
-  // an error into `accessor_->OnCreditCardFetched()`, and end the flow.
+  // an error into the `OnCreditCardFetchedCallback`, and end the flow.
   if (response.card) {
     // TODO(crbug/1478392): Deprecate `response.cvc` and `response.card.cvc`.
     card_ = std::make_unique<CreditCard>(*response.card);
@@ -631,10 +632,10 @@
     // If ShouldRespondImmediately() returns true,
     // |should_register_card_with_fido| should be false.
     DCHECK(!should_register_card_with_fido);
-    accessor_->OnCreditCardFetched(response.did_succeed
-                                       ? CreditCardFetchResult::kSuccess
-                                       : CreditCardFetchResult::kTransientError,
-                                   card_.get());
+    std::move(on_credit_card_fetched_callback_)
+        .Run(response.did_succeed ? CreditCardFetchResult::kSuccess
+                                  : CreditCardFetchResult::kTransientError,
+             card_.get());
     unmask_auth_flow_type_ = UnmaskAuthFlowType::kNone;
   } else if (should_register_card_with_fido) {
 #if !BUILDFLAG(IS_IOS)
@@ -752,8 +753,8 @@
     if (response.card) {
       card_ = std::make_unique<CreditCard>(*response.card);
     }
-    accessor_->OnCreditCardFetched(CreditCardFetchResult::kSuccess,
-                                   card_.get());
+    std::move(on_credit_card_fetched_callback_)
+        .Run(CreditCardFetchResult::kSuccess, card_.get());
     Reset();
   } else if (
       response.failure_type ==
@@ -773,7 +774,7 @@
             /*is_permanent_error=*/response.failure_type ==
             payments::FullCardRequest::
                 VIRTUAL_CARD_RETRIEVAL_PERMANENT_FAILURE));
-    accessor_->OnCreditCardFetched(result, nullptr);
+    std::move(on_credit_card_fetched_callback_).Run(result, nullptr);
 
     if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
       autofill_metrics::LogServerCardUnmaskResult(
@@ -796,8 +797,8 @@
 
 void CreditCardAccessManager::OnFidoAuthorizationComplete(bool did_succeed) {
   if (did_succeed) {
-    accessor_->OnCreditCardFetched(CreditCardFetchResult::kSuccess,
-                                   card_.get());
+    std::move(on_credit_card_fetched_callback_)
+        .Run(CreditCardFetchResult::kSuccess, card_.get());
     form_event_logger_->LogCardUnmaskAuthenticationPromptCompleted(
         unmask_auth_flow_type_);
   }
@@ -810,18 +811,18 @@
   // Save credit card for caching purpose. CVC is also saved if response
   // contains CVC. `response.card` can be nullptr in the case of an error in the
   // response. If the response has an error, we will safely pass nullptr and
-  // that it is an error into `accessor_->OnCreditCardFetched()`, and end the
+  // that it is an error into the `OnCreditCardFetchedCallback` and end the
   // flow.
   if (response.card) {
     card_ = std::make_unique<CreditCard>(*response.card);
     card_->set_cvc(response.cvc);
   }
-  accessor_->OnCreditCardFetched(
-      response.result == CreditCardOtpAuthenticator::OtpAuthenticationResponse::
-                             Result::kSuccess
-          ? CreditCardFetchResult::kSuccess
-          : CreditCardFetchResult::kTransientError,
-      card_.get());
+  std::move(on_credit_card_fetched_callback_)
+      .Run(response.result == CreditCardOtpAuthenticator::
+                                  OtpAuthenticationResponse::Result::kSuccess
+               ? CreditCardFetchResult::kSuccess
+               : CreditCardFetchResult::kTransientError,
+           card_.get());
 
   autofill_metrics::ServerCardUnmaskResult result;
   switch (response.result) {
@@ -1158,12 +1159,12 @@
     // `StartDeviceAuthenticationForFilling()` will asynchronously trigger
     // the re-authentication flow, so we should avoid calling `Reset()`
     // until the re-authentication flow is complete.
-    StartDeviceAuthenticationForFilling(accessor_, card_.get());
+    StartDeviceAuthenticationForFilling(card_.get());
   } else {
     // Fill immediately if local card or full server card, as we do not need to
     // authenticate the user.
-    accessor_->OnCreditCardFetched(CreditCardFetchResult::kSuccess,
-                                   card_.get());
+    std::move(on_credit_card_fetched_callback_)
+        .Run(CreditCardFetchResult::kSuccess, card_.get());
 
     // This local or full server card autofill flow did not have any interactive
     // authentication, so notify the FormDataImporter of this.
@@ -1171,9 +1172,9 @@
         ->SetCardRecordTypeIfNonInteractiveAuthenticationFlowCompleted(
             card_->record_type());
 
-    // `accessor_->OnCreditCardFetched()` makes a copy of `card` and `cvc`
-    // before it asynchronously fills them into the form. Thus we can safely
-    // call `Reset()` here, and we should as from this class' point of view the
+    // `OnCreditCardFetchedCallback` makes a copy of `card` and `cvc` before it
+    // asynchronously fills them into the form. Thus we can safely call
+    // `Reset()` here, and we should as from this class' point of view the
     // authentication flow is complete.
     Reset();
   }
@@ -1188,7 +1189,7 @@
       // If the response indicates no further authentication is required, then
       // complete card information has been fetched from the server (this is
       // ensured in CreditCardRiskBasedAuthenticator). Pass the unmasked card to
-      // `accessor_` and end the session.
+      // `OnCreditCardFetchedCallback` and end the session.
       CHECK(response.card.has_value());
       card_ = std::make_unique<CreditCard>(response.card.value());
       // Although the card being retrieved is a masked server card, the
@@ -1220,8 +1221,8 @@
       // Shows error dialog to users if the authentication failed.
       client_->CloseAutofillProgressDialog(
           /*show_confirmation_before_closing=*/false);
-      accessor_->OnCreditCardFetched(CreditCardFetchResult::kTransientError,
-                                     nullptr);
+      std::move(on_credit_card_fetched_callback_)
+          .Run(CreditCardFetchResult::kTransientError, nullptr);
       client_->ShowAutofillErrorDialog(response.error_dialog_context);
       Reset();
       break;
@@ -1244,7 +1245,8 @@
     if (!response_details.real_pan.empty()) {
       // If the real pan is not empty, then complete card information has been
       // fetched from the server (this is ensured in PaymentsNetworkInterface).
-      // Pass the unmasked card to `accessor_` and end the session.
+      // Pass the unmasked card to `OnCreditCardFetchedCallback` and end the
+      // session.
       CHECK_EQ(response_details.card_type,
                AutofillClient::PaymentsRpcCardType::kVirtualCard);
       card_->SetNumber(base::UTF8ToUTF16(response_details.real_pan));
@@ -1276,8 +1278,8 @@
   // Close the progress dialog without showing the confirmation.
   client_->CloseAutofillProgressDialog(
       /*show_confirmation_before_closing=*/false);
-  accessor_->OnCreditCardFetched(CreditCardFetchResult::kTransientError,
-                                 nullptr);
+  std::move(on_credit_card_fetched_callback_)
+      .Run(CreditCardFetchResult::kTransientError, nullptr);
 
   autofill_metrics::ServerCardUnmaskResult unmask_result;
   if (result ==
@@ -1330,12 +1332,12 @@
             // calling `Reset()` until the re-authentication flow is
             // complete.
             &CreditCardAccessManager::StartDeviceAuthenticationForFilling,
-            weak_ptr_factory_.GetWeakPtr(), accessor_, card_.get()));
+            weak_ptr_factory_.GetWeakPtr(), card_.get()));
   } else {
     client_->CloseAutofillProgressDialog(
         /*show_confirmation_before_closing=*/true);
-    accessor_->OnCreditCardFetched(CreditCardFetchResult::kSuccess,
-                                   card_.get());
+    std::move(on_credit_card_fetched_callback_)
+        .Run(CreditCardFetchResult::kSuccess, card_.get());
 
     // If the server returned a successful response along with the card's
     // real PAN without requiring interactive authentication, set the
@@ -1354,10 +1356,10 @@
           autofill_metrics::VirtualCardUnmaskFlowType::kUnspecified);
     }
 
-    // `accessor_->OnCreditCardFetched()` makes a copy of `card` and `cvc`
-    // before it asynchronously fills them into the form. Thus we can safely
-    // call `Reset()` here, and we should as from this class' point of view
-    // the authentication flow is complete.
+    // `OnCreditCardFetchedCallback` makes a copy of `card` and `cvc` before it
+    // asynchronously fills them into the form. Thus we can safely call
+    // `Reset()` here, and we should as from this class' point of view the
+    // authentication flow is complete.
     Reset();
   }
 }
@@ -1396,8 +1398,8 @@
   if (!selected_challenge_option_ ||
       virtual_card_unmask_response_details_.context_token.empty()) {
     NOTREACHED();
-    accessor_->OnCreditCardFetched(CreditCardFetchResult::kTransientError,
-                                   nullptr);
+    std::move(on_credit_card_fetched_callback_)
+        .Run(CreditCardFetchResult::kTransientError, nullptr);
     client_->ShowAutofillErrorDialog(
         AutofillErrorDialogContext::WithVirtualCardPermanentOrTemporaryError(
             /*is_permanent_error=*/false));
@@ -1425,8 +1427,8 @@
 }
 
 void CreditCardAccessManager::OnVirtualCardUnmaskCancelled() {
-  accessor_->OnCreditCardFetched(CreditCardFetchResult::kTransientError,
-                                 nullptr);
+  std::move(on_credit_card_fetched_callback_)
+      .Run(CreditCardFetchResult::kTransientError, nullptr);
 
   if (unmask_auth_flow_type_ == UnmaskAuthFlowType::kOtp ||
       unmask_auth_flow_type_ == UnmaskAuthFlowType::kOtpFallbackFromFido) {
@@ -1466,8 +1468,8 @@
 }
 
 void CreditCardAccessManager::OnRiskBasedAuthenticationCancelled() {
-  accessor_->OnCreditCardFetched(CreditCardFetchResult::kTransientError,
-                                 nullptr);
+  std::move(on_credit_card_fetched_callback_)
+      .Run(CreditCardFetchResult::kTransientError, nullptr);
 
   // TODO(crbug.com/1470933): Log the cancel metrics.
   Reset();
@@ -1554,7 +1556,6 @@
 }
 
 void CreditCardAccessManager::StartDeviceAuthenticationForFilling(
-    base::WeakPtr<Accessor> accessor,
     const CreditCard* card) {
   is_authentication_in_progress_ = true;
 
@@ -1570,8 +1571,7 @@
       l10n_util::GetStringUTF16(IDS_PAYMENTS_AUTOFILL_FILLING_MANDATORY_REAUTH),
       base::BindOnce(
           &CreditCardAccessManager::OnDeviceAuthenticationResponseForFilling,
-          weak_ptr_factory_.GetWeakPtr(), accessor, authentication_method,
-          card));
+          weak_ptr_factory_.GetWeakPtr(), authentication_method, card));
 #elif BUILDFLAG(IS_ANDROID)
   // TODO(crbug.com/1427216): Convert this to
   // MandatoryReauthManager::AuthenticateWithMessage() with the correct message
@@ -1579,15 +1579,13 @@
   client_->GetOrCreatePaymentsMandatoryReauthManager()->Authenticate(
       base::BindOnce(
           &CreditCardAccessManager::OnDeviceAuthenticationResponseForFilling,
-          weak_ptr_factory_.GetWeakPtr(), accessor, authentication_method,
-          card));
+          weak_ptr_factory_.GetWeakPtr(), authentication_method, card));
 #else
   NOTREACHED_NORETURN();
 #endif
 }
 
 void CreditCardAccessManager::OnDeviceAuthenticationResponseForFilling(
-    base::WeakPtr<Accessor> accessor,
     payments::MandatoryReauthAuthenticationMethod authentication_method,
     const CreditCard* card,
     bool successful_auth) {
@@ -1599,10 +1597,10 @@
           : autofill_metrics::MandatoryReauthAuthenticationFlowEvent::
                 kFlowFailed);
   CHECK(card);
-  accessor->OnCreditCardFetched(successful_auth
-                                    ? CreditCardFetchResult::kSuccess
-                                    : CreditCardFetchResult::kTransientError,
-                                card);
+  std::move(on_credit_card_fetched_callback_)
+      .Run(successful_auth ? CreditCardFetchResult::kSuccess
+                           : CreditCardFetchResult::kTransientError,
+           card);
   // TODO(crbug.com/1427216): Add logging for the payments autofill device
   // authentication flow.
   // `accessor->OnCreditCardFetched()` makes a copy of `card` and `cvc` before
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager.h b/components/autofill/core/browser/payments/credit_card_access_manager.h
index 0097966..c465f13 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager.h
+++ b/components/autofill/core/browser/payments/credit_card_access_manager.h
@@ -11,6 +11,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/functional/callback_forward.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
@@ -95,12 +96,8 @@
       public CreditCardOtpAuthenticator::Requester,
       public CreditCardRiskBasedAuthenticator::Requester {
  public:
-  class Accessor {
-   public:
-    virtual ~Accessor() = default;
-    virtual void OnCreditCardFetched(CreditCardFetchResult result,
-                                     const CreditCard* credit_card) = 0;
-  };
+  using OnCreditCardFetchedCallback =
+      base::OnceCallback<void(CreditCardFetchResult, const CreditCard*)>;
 
   CreditCardAccessManager(AutofillDriver* driver,
                           AutofillClient* client,
@@ -129,9 +126,10 @@
   // Makes a call to Google Payments to retrieve authentication details.
   void PrepareToFetchCreditCard();
 
-  // Calls |accessor->OnCreditCardFetched()| once credit card is fetched.
-  virtual void FetchCreditCard(const CreditCard* card,
-                               base::WeakPtr<Accessor> accessor);
+  // `on_credit_card_fetched` is run once `card` is fetched.
+  virtual void FetchCreditCard(
+      const CreditCard* card,
+      OnCreditCardFetchedCallback on_credit_card_fetched);
 
   // Checks whether we should offer risk-based authentication for masked server
   // card retrieval.
@@ -422,14 +420,14 @@
 
   // Starts the device authentication flow during a payments autofill form fill.
   // `OnDeviceAuthenticationResponseForFilling()` will be invoked when we
-  // receive a response from the device authentication. `accessor` will be used
-  // to handle the response of the authentication, and possibly fill the card
-  // into the form. `card` is the card that needs to be filled. This function
-  // should only be called on platforms where DeviceAuthenticator is present.
+  // receive a response from the device authentication.
+  // `on_credit_card_fetched_callback_` will be used to handle the response of
+  // the authentication, and possibly fill the card into the form. `card` is the
+  // card that needs to be filled. This function should only be called on
+  // platforms where DeviceAuthenticator is present.
   // TODO(crbug.com/1447084): Move authentication logic for re-auth into
   // MandatoryReauthManager.
-  void StartDeviceAuthenticationForFilling(base::WeakPtr<Accessor> accessor,
-                                           const CreditCard* card);
+  void StartDeviceAuthenticationForFilling(const CreditCard* card);
 
   // Callback function invoked when we receive a response from a mandatory
   // re-auth authentication in a flow where we might fill the card after the
@@ -440,7 +438,6 @@
   // TODO(crbug.com/1447084): Move authentication logic for re-auth into
   // MandatoryReauthManager.
   void OnDeviceAuthenticationResponseForFilling(
-      base::WeakPtr<Accessor> accessor,
       payments::MandatoryReauthAuthenticationMethod authentication_method,
       const CreditCard* card,
       bool successful_auth);
@@ -530,8 +527,10 @@
   // unnecessary calls to payments.
   bool unmask_details_request_in_progress_ = false;
 
-  // The object attempting to access a card.
-  base::WeakPtr<Accessor> accessor_;
+  // Callback to notify the caller of the access manager when fetching the
+  // card has finished. Only has a meaningful value when an authentication is in
+  // progress.
+  OnCreditCardFetchedCallback on_credit_card_fetched_callback_;
 
   // Used only in virtual card authentication to differentiate between
   // authentication methods. Set when a challenge option is selected, and we are
diff --git a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
index 156585c..da67ccd 100644
--- a/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/credit_card_access_manager_unittest.cc
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/base64.h"
+#include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/field_trial.h"
 #include "base/strings/utf_string_conversions.h"
@@ -91,7 +92,9 @@
 }
 #endif
 
-class TestAccessor : public CreditCardAccessManager::Accessor {
+// Implements an `OnCreditCardFetched()` callback that stores the values it
+// receives.
+class TestAccessor {
  public:
   TestAccessor() = default;
 
@@ -100,7 +103,7 @@
   }
 
   void OnCreditCardFetched(CreditCardFetchResult result,
-                           const CreditCard* card) override {
+                           const CreditCard* card) {
     result_ = result;
     if (result == CreditCardFetchResult::kSuccess) {
       DCHECK(card);
@@ -416,8 +419,9 @@
     // |is_user_verifiable_| related logic from CreditCardAccessManager to
     // CreditCardFidoAuthenticator.
     credit_card_access_manager().is_user_verifiable_ = is_user_verifiable;
-    credit_card_access_manager().FetchCreditCard(virtual_card,
-                                                 accessor_->GetWeakPtr());
+    credit_card_access_manager().FetchCreditCard(
+        virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                     accessor_->GetWeakPtr()));
 
     // This checks risk-based authentication flow is successfully invoked,
     // because it is always the very first authentication flow in a VCN
@@ -728,7 +732,9 @@
   // TODO(crbug/1489440): Extract shared boilerplate code out for
   // CreditCardAccessManagerMandatoryReauthTest tests.
   SetUpDeviceAuthenticatorResponseMock();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
 
   // The only time we should expect an error is if mandatory re-auth is
   // enabled, but the mandatory re-auth authentication was not successful.
@@ -773,8 +779,9 @@
   CreditCard* virtual_card = personal_data().GetCreditCardByGUID(kTestGUID);
   virtual_card->set_record_type(CreditCard::RecordType::kVirtualCard);
 
-  credit_card_access_manager().FetchCreditCard(virtual_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                   accessor_->GetWeakPtr()));
 
   // This checks risk-based authentication flow is successfully invoked,
   // because it is always the very first authentication flow in a VCN
@@ -839,8 +846,9 @@
   CreditCard* masked_server_card =
       personal_data().GetCreditCardByGUID(kTestGUID);
 
-  credit_card_access_manager().FetchCreditCard(masked_server_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      masked_server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                         accessor_->GetWeakPtr()));
 
   // Ensures CreditCardRiskBasedAuthenticator::Authenticate is successfully
   // invoked.
@@ -915,7 +923,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
 
   EXPECT_EQ(accessor_->result(), CreditCardFetchResult::kSuccess);
   EXPECT_EQ(kTestNumber16, accessor_->number());
@@ -937,8 +947,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(nullptr,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      nullptr, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                              accessor_->GetWeakPtr()));
   EXPECT_NE(accessor_->result(), CreditCardFetchResult::kSuccess);
 }
 
@@ -963,7 +974,9 @@
     credit_card_access_manager().PrepareToFetchCreditCard();
     WaitForCallbacks();
 
-    credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+    credit_card_access_manager().FetchCreditCard(
+        card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                             accessor_->GetWeakPtr()));
     histogram_tester.ExpectUniqueSample(
         flow_events_histogram_name,
         CreditCardFormEventLogger::UnmaskAuthFlowEvent::kPromptShown, 1);
@@ -1002,7 +1015,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
 
   EXPECT_TRUE(GetRealPanForCVCAuth(
       AutofillClient::PaymentsRpcResult::kNetworkError, std::string()));
@@ -1018,7 +1033,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
 
   EXPECT_TRUE(GetRealPanForCVCAuth(
       AutofillClient::PaymentsRpcResult::kPermanentFailure, std::string()));
@@ -1030,7 +1047,9 @@
   CreateServerCard(kTestGUID, kTestNumber);
   CreditCard* card = personal_data().GetCreditCardByGUID(kTestGUID);
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
 
   EXPECT_TRUE(GetRealPanForCVCAuth(
       AutofillClient::PaymentsRpcResult::kTryAgainFailure, std::string()));
@@ -1159,7 +1178,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
   histogram_tester.ExpectUniqueSample(
       flow_events_histogram_name,
@@ -1214,7 +1235,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
 
   // FIDO Success.
@@ -1243,7 +1266,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
 
   // FIDO Success.
@@ -1281,7 +1306,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
   histogram_tester.ExpectUniqueSample(
       flow_events_fido_histogram_name,
@@ -1339,7 +1366,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
 
   // FIDO Failure.
@@ -1383,7 +1412,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
 
   // FIDO Failure.
@@ -1407,7 +1438,9 @@
   GetFIDOAuthenticator()->SetUserVerifiable(true);
   SetCreditCardFIDOAuthEnabled(true);
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   InvokeUnmaskDetailsTimeout();
   WaitForCallbacks();
 
@@ -1446,8 +1479,9 @@
       task_environment_.FastForwardBy(base::Seconds(4));
       WaitForCallbacks();
 
-      credit_card_access_manager().FetchCreditCard(local_card,
-                                                   accessor_->GetWeakPtr());
+      credit_card_access_manager().FetchCreditCard(
+          local_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                     accessor_->GetWeakPtr()));
       WaitForCallbacks();
 
       histogram_tester.ExpectUniqueSample(
@@ -1462,8 +1496,9 @@
 
       ResetFetchCreditCard();
       credit_card_access_manager().PrepareToFetchCreditCard();
-      credit_card_access_manager().FetchCreditCard(server_card,
-                                                   accessor_->GetWeakPtr());
+      credit_card_access_manager().FetchCreditCard(
+          server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                      accessor_->GetWeakPtr()));
       task_environment_.FastForwardBy(base::Seconds(4));
       WaitForCallbacks();
 
@@ -1493,8 +1528,9 @@
       credit_card_access_manager().PrepareToFetchCreditCard();
       WaitForCallbacks();
 
-      credit_card_access_manager().FetchCreditCard(server_card,
-                                                   accessor_->GetWeakPtr());
+      credit_card_access_manager().FetchCreditCard(
+          server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                      accessor_->GetWeakPtr()));
       WaitForCallbacks();
 
       histogram_tester.ExpectUniqueSample(
@@ -1532,8 +1568,9 @@
 
     ResetFetchCreditCard();
     credit_card_access_manager().PrepareToFetchCreditCard();
-    credit_card_access_manager().FetchCreditCard(server_card,
-                                                 accessor_->GetWeakPtr());
+    credit_card_access_manager().FetchCreditCard(
+        server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                    accessor_->GetWeakPtr()));
 
     // Mock a delayed response.
     InvokeDelayedGetUnmaskDetailsResponse();
@@ -1558,8 +1595,9 @@
 
     ResetFetchCreditCard();
     credit_card_access_manager().PrepareToFetchCreditCard();
-    credit_card_access_manager().FetchCreditCard(server_card,
-                                                 accessor_->GetWeakPtr());
+    credit_card_access_manager().FetchCreditCard(
+        server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                    accessor_->GetWeakPtr()));
     task_environment_.FastForwardBy(base::Seconds(4));
     WaitForCallbacks();
 
@@ -1593,7 +1631,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   InvokeUnmaskDetailsTimeout();
   WaitForCallbacks();
   histogram_tester.ExpectUniqueSample(
@@ -1649,7 +1689,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
 
   // Expect CVC prompt to be invoked.
@@ -1672,7 +1714,9 @@
   GetFIDOAuthenticator()->SetUserVerifiable(true);
   SetCreditCardFIDOAuthEnabled(false);
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   InvokeUnmaskDetailsTimeout();
   WaitForCallbacks();
 
@@ -1724,7 +1768,9 @@
   GetFIDOAuthenticator()->SetUserVerifiable(true);
   SetCreditCardFIDOAuthEnabled(false);
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   InvokeUnmaskDetailsTimeout();
   WaitForCallbacks();
 
@@ -1765,7 +1811,9 @@
   GetFIDOAuthenticator()->SetUserVerifiable(true);
   SetCreditCardFIDOAuthEnabled(false);
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   InvokeUnmaskDetailsTimeout();
   WaitForCallbacks();
 
@@ -1803,7 +1851,9 @@
   GetFIDOAuthenticator()->SetUserVerifiable(true);
   SetCreditCardFIDOAuthEnabled(false);
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   InvokeUnmaskDetailsTimeout();
   WaitForCallbacks();
 
@@ -1838,7 +1888,9 @@
   payments_network_interface().ShouldReturnUnmaskDetailsImmediately(true);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   InvokeUnmaskDetailsTimeout();
   WaitForCallbacks();
 
@@ -1876,7 +1928,9 @@
   payments_network_interface().AllowFidoRegistration(true);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
 
   // Mock user and payments response.
@@ -1935,7 +1989,9 @@
   payments_network_interface().AllowFidoRegistration(true);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
 
   // Mock user and payments response.
@@ -1972,7 +2028,9 @@
   payments_network_interface().AllowFidoRegistration(true);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
 
   // Mock user and payments response.
@@ -2010,7 +2068,9 @@
   payments_network_interface().AllowFidoRegistration(true);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   InvokeUnmaskDetailsTimeout();
   WaitForCallbacks();
 
@@ -2057,7 +2117,9 @@
   payments_network_interface().AllowFidoRegistration(true);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   WaitForCallbacks();
 
   // Mock user and payments response.
@@ -2209,8 +2271,9 @@
     }
 
     credit_card_access_manager().PrepareToFetchCreditCard();
-    credit_card_access_manager().FetchCreditCard(GetCreditCard(),
-                                                 accessor_->GetWeakPtr());
+    credit_card_access_manager().FetchCreditCard(
+        GetCreditCard(), base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                        accessor_->GetWeakPtr()));
   }
 
   bool IsVirtualCard() { return std::get<0>(GetParam()); }
@@ -2374,7 +2437,9 @@
   payments_network_interface().ShouldReturnUnmaskDetailsImmediately(false);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
 
   // Ensure the auth flow type is CVC because no unmask detail response is
   // returned and local pref denotes that user is opted out.
@@ -2416,7 +2481,9 @@
   SetCreditCardFIDOAuthEnabled(/*enabled=*/false);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
 
   // Ensure that the local pref is still unchanged after unmask detail returns.
   EXPECT_FALSE(IsCreditCardFIDOAuthEnabled());
@@ -2452,7 +2519,9 @@
   SetCreditCardFIDOAuthEnabled(/*enabled=*/false);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
 
   // Ensure that the local pref is still unchanged after unmask detail returns.
   EXPECT_FALSE(IsCreditCardFIDOAuthEnabled());
@@ -2487,7 +2556,9 @@
   SetCreditCardFIDOAuthEnabled(/*enabled=*/false);
 
   credit_card_access_manager().PrepareToFetchCreditCard();
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
 
   // Ensure that the local pref is still unchanged after unmask detail returns.
   EXPECT_FALSE(IsCreditCardFIDOAuthEnabled());
@@ -2561,7 +2632,9 @@
   credit_card_access_manager().PrepareToFetchCreditCard();
   WaitForCallbacks();
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   histogram_tester.ExpectUniqueSample(
       flow_events_histogram_name,
       CreditCardFormEventLogger::UnmaskAuthFlowEvent::kPromptShown, 1);
@@ -2582,7 +2655,9 @@
 
   EXPECT_FALSE(IsAuthenticationInProgress());
 
-  credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                           accessor_->GetWeakPtr()));
   EXPECT_TRUE(IsAuthenticationInProgress());
 
   EXPECT_TRUE(GetRealPanForCVCAuth(AutofillClient::PaymentsRpcResult::kSuccess,
@@ -2601,11 +2676,13 @@
   CreateServerCard(kTestGUID, kTestNumber, /*masked=*/true);
   CreditCard* masked_card = personal_data().GetCreditCardByGUID(kTestGUID);
 
-  credit_card_access_manager().FetchCreditCard(masked_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      masked_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                  accessor_->GetWeakPtr()));
   histogram_tester.ExpectBucketCount("Autofill.UsedCachedServerCard", 1, 1);
-  credit_card_access_manager().FetchCreditCard(masked_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      masked_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                  accessor_->GetWeakPtr()));
   histogram_tester.ExpectBucketCount("Autofill.UsedCachedServerCard", 2, 1);
 
   // Create a virtual card.
@@ -2618,8 +2695,9 @@
 
   // Mocks that user selects the virtual card option of the masked card.
   masked_card->set_record_type(CreditCard::RecordType::kVirtualCard);
-  credit_card_access_manager().FetchCreditCard(masked_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      masked_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                  accessor_->GetWeakPtr()));
 
   histogram_tester.ExpectBucketCount("Autofill.UsedCachedVirtualCard", 1, 1);
 }
@@ -2667,7 +2745,9 @@
       features::kAutofillEnableFpanRiskBasedAuthentication};
 
   void MockRiskBasedAuthSucceedsWithoutPanReturned(CreditCard* card) {
-    credit_card_access_manager().FetchCreditCard(card, accessor_->GetWeakPtr());
+    credit_card_access_manager().FetchCreditCard(
+        card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                             accessor_->GetWeakPtr()));
 
     // Ensures CreditCardRiskBasedAuthenticator::Authenticate is successfully
     // invoked.
@@ -2694,8 +2774,9 @@
   CreditCard* masked_server_card =
       CreateServerCard(kTestGUID, test_number, /*masked=*/true, kTestServerId);
 
-  credit_card_access_manager().FetchCreditCard(masked_server_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      masked_server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                         accessor_->GetWeakPtr()));
 
   // Ensures CreditCardRiskBasedAuthenticator::Authenticate is successfully
   // invoked.
@@ -2733,8 +2814,9 @@
   CreditCard* masked_server_card =
       CreateServerCard(kTestGUID, kTestNumber, /*masked=*/true, kTestServerId);
 
-  credit_card_access_manager().FetchCreditCard(masked_server_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      masked_server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                         accessor_->GetWeakPtr()));
 
   // Ensures CreditCardRiskBasedAuthenticator::Authenticate is successfully
   // invoked.
@@ -2760,8 +2842,9 @@
   CreditCard* masked_server_card =
       CreateServerCard(kTestGUID, kTestNumber, /*masked=*/true, kTestServerId);
 
-  credit_card_access_manager().FetchCreditCard(masked_server_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      masked_server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                         accessor_->GetWeakPtr()));
 
   // Ensures CreditCardRiskBasedAuthenticator::Authenticate is successfully
   // invoked.
@@ -2787,8 +2870,9 @@
   CreditCard* masked_server_card =
       personal_data().GetCreditCardByGUID(kTestGUID);
 
-  credit_card_access_manager().FetchCreditCard(masked_server_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      masked_server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                         accessor_->GetWeakPtr()));
 
   // Ensures CreditCardRiskBasedAuthenticator::Authenticate is not invoked.
   ASSERT_FALSE(autofill_client_.risk_based_authentication_invoked());
@@ -2837,8 +2921,9 @@
   credit_card_access_manager().is_user_verifiable_ = true;
   fido_authenticator().set_is_user_opted_in(true);
 
-  credit_card_access_manager().FetchCreditCard(masked_server_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      masked_server_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                         accessor_->GetWeakPtr()));
 
   // Ensures CreditCardRiskBasedAuthenticator::Authenticate is successfully
   // invoked.
@@ -2991,8 +3076,9 @@
   CreditCard* virtual_card = personal_data().GetCreditCardByGUID(kTestGUID);
   virtual_card->set_record_type(CreditCard::RecordType::kVirtualCard);
 
-  credit_card_access_manager().FetchCreditCard(virtual_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                   accessor_->GetWeakPtr()));
 
   // This checks risk-based authentication flow is successfully invoked,
   // because it is always the very first authentication flow in a VCN
@@ -3198,8 +3284,9 @@
   credit_card_access_manager().is_user_verifiable_ = true;
   fido_authenticator().set_is_user_opted_in(true);
 
-  credit_card_access_manager().FetchCreditCard(virtual_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                   accessor_->GetWeakPtr()));
 
   // This checks risk-based authentication flow is successfully invoked,
   // because it is always the very first authentication flow in a VCN
@@ -3256,8 +3343,9 @@
   credit_card_access_manager().is_user_verifiable_ = true;
   fido_authenticator().set_is_user_opted_in(true);
 
-  credit_card_access_manager().FetchCreditCard(virtual_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                   accessor_->GetWeakPtr()));
 
   // This checks risk-based authentication flow is successfully invoked,
   // because it is always the very first authentication flow in a VCN
@@ -3378,8 +3466,9 @@
   credit_card_access_manager().is_user_verifiable_ = true;
   fido_authenticator().set_is_user_opted_in(false);
 
-  credit_card_access_manager().FetchCreditCard(virtual_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                   accessor_->GetWeakPtr()));
 
   // This checks risk-based authentication flow is successfully invoked,
   // because it is always the very first authentication flow in a VCN
@@ -3426,8 +3515,9 @@
   fido_authenticator().set_is_user_opted_in(true);
 #endif
 
-  credit_card_access_manager().FetchCreditCard(virtual_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                   accessor_->GetWeakPtr()));
 
   // This checks risk-based authentication flow is successfully invoked,
   // because it is always the very first authentication flow in a VCN
@@ -3471,8 +3561,9 @@
   fido_authenticator().set_is_user_opted_in(true);
 #endif
 
-  credit_card_access_manager().FetchCreditCard(virtual_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                   accessor_->GetWeakPtr()));
 
   // This checks risk-based authentication flow is successfully invoked,
   // because it is always the very first authentication flow in a VCN
@@ -3507,8 +3598,9 @@
   CreateServerCard(kTestGUID, kTestNumber, /*masked=*/false, kTestServerId);
   CreditCard* virtual_card = personal_data().GetCreditCardByGUID(kTestGUID);
   virtual_card->set_record_type(CreditCard::RecordType::kVirtualCard);
-  credit_card_access_manager().FetchCreditCard(virtual_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                   accessor_->GetWeakPtr()));
 
   AutofillErrorDialogContext autofill_error_dialog_context;
   autofill_error_dialog_context.server_returned_title =
@@ -3550,8 +3642,9 @@
   fido_authenticator().set_is_user_opted_in(true);
 #endif
 
-  credit_card_access_manager().FetchCreditCard(virtual_card,
-                                               accessor_->GetWeakPtr());
+  credit_card_access_manager().FetchCreditCard(
+      virtual_card, base::BindOnce(&TestAccessor::OnCreditCardFetched,
+                                   accessor_->GetWeakPtr()));
 
   // This checks risk-based authentication flow is successfully invoked,
   // because it is always the very first authentication flow in a VCN
diff --git a/components/autofill/core/browser/payments/iban_save_manager_unittest.cc b/components/autofill/core/browser/payments/iban_save_manager_unittest.cc
index abe8c36..9b9500e 100644
--- a/components/autofill/core/browser/payments/iban_save_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/iban_save_manager_unittest.cc
@@ -139,7 +139,7 @@
 TEST_F(IbanSaveManagerTest, AttemptToOfferSave_LocalIban_ShouldOfferSave) {
   Iban iban;
   iban.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue)));
-  personal_data().AddIban(iban);
+  personal_data().AddAsLocalIban(iban);
 
   Iban another_iban;
   another_iban.set_value(iban.value());
@@ -170,7 +170,7 @@
 TEST_F(IbanSaveManagerTest, ShouldOfferUploadSave_LocalIban) {
   Iban iban;
   iban.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue)));
-  personal_data().AddIban(iban);
+  personal_data().AddAsLocalIban(iban);
 
   Iban another_iban;
   another_iban.set_value(iban.value());
@@ -464,7 +464,7 @@
 TEST_F(IbanSaveManagerTest, OfferUploadSave_LocalIban_Success) {
   Iban local_iban;
   local_iban.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue)));
-  personal_data().AddIban(local_iban);
+  personal_data().AddAsLocalIban(local_iban);
   Iban another_iban;
   another_iban.set_value(local_iban.value());
 
@@ -483,7 +483,7 @@
        OfferUploadSave_LocalIban_Failure_LocalSaveNotOffered) {
   Iban local_iban;
   local_iban.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue)));
-  personal_data().AddIban(local_iban);
+  personal_data().AddAsLocalIban(local_iban);
   Iban another_iban;
   another_iban.set_value(local_iban.value());
 
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc
index 72aeeca..a3016eaf 100644
--- a/components/autofill/core/browser/personal_data_manager.cc
+++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -908,7 +908,7 @@
   AddProfile(account_profile);
 }
 
-std::string PersonalDataManager::AddIban(Iban iban) {
+std::string PersonalDataManager::AddAsLocalIban(Iban iban) {
   CHECK_EQ(iban.record_type(), Iban::kUnknown);
   // IBAN shares the same pref with payment methods enablement toggle.
   if (!IsAutofillPaymentMethodsEnabled()) {
@@ -2207,9 +2207,10 @@
 
 std::string PersonalDataManager::OnAcceptedLocalIbanSave(Iban imported_iban) {
   DCHECK(!imported_iban.value().empty());
-  // If an existing IBAN is found, call `UpdateIban()`, otherwise, `AddIban()`.
-  // `local_ibans_` will be in sync with the local web database as of
-  // `Refresh()` which will be called by both `UpdateIban()` and `AddIban()`.
+  // If an existing IBAN is found, call `UpdateIban()`, otherwise,
+  // `AddAsLocalIban()`. `local_ibans_` will be in sync with the local web
+  // database as of `Refresh()` which will be called by both `UpdateIban()` and
+  // `AddAsLocalIban()`.
   for (auto& iban : local_ibans_) {
     if (iban->value().compare(imported_iban.value()) == 0) {
       // Set the GUID of the IBAN to the one that matches it in
@@ -2219,7 +2220,7 @@
       return UpdateIban(imported_iban);
     }
   }
-  return AddIban(std::move(imported_iban));
+  return AddAsLocalIban(std::move(imported_iban));
 }
 
 void PersonalDataManager::SetSyncService(syncer::SyncService* sync_service) {
diff --git a/components/autofill/core/browser/personal_data_manager.h b/components/autofill/core/browser/personal_data_manager.h
index 34e0f7a5..5d7d602 100644
--- a/components/autofill/core/browser/personal_data_manager.h
+++ b/components/autofill/core/browser/personal_data_manager.h
@@ -300,7 +300,7 @@
   // 1) IBAN saving must be enabled.
   // 2) No IBAN exists in `local_ibans_` which has the same guid as`iban`.
   // 3) Local database is available.
-  virtual std::string AddIban(Iban iban);
+  virtual std::string AddAsLocalIban(Iban iban);
 
   // Updates `iban` which already exists in the web database. This can only
   // be used on local ibans. Returns the guid of `iban` if the update is
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc
index c5c2486..07c93d3 100644
--- a/components/autofill/core/browser/personal_data_manager_unittest.cc
+++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -316,7 +316,7 @@
   }
 
   void AddLocalIban(Iban& iban) {
-    iban.set_identifier(Iban::Guid(personal_data_->AddIban(iban)));
+    iban.set_identifier(Iban::Guid(personal_data_->AddAsLocalIban(iban)));
     PersonalDataProfileTaskWaiter(*personal_data_).Wait();
     iban.set_record_type(Iban::kLocalIban);
   }
@@ -1129,8 +1129,8 @@
   Iban iban1;
   iban1.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue_1)));
 
-  personal_data_->AddIban(iban);
-  personal_data_->AddIban(iban1);
+  personal_data_->AddAsLocalIban(iban);
+  personal_data_->AddAsLocalIban(iban1);
 
   EXPECT_EQ(0U, personal_data_->GetLocalIbans().size());
 }
@@ -1141,7 +1141,7 @@
   Iban iban;
   iban.set_value(base::UTF8ToUTF16(std::string(test::kIbanValue)));
 
-  personal_data_->AddIban(iban);
+  personal_data_->AddAsLocalIban(iban);
   PersonalDataProfileTaskWaiter(*personal_data_).Wait();
   // Adding an IBAN permanently enables the pref.
   EXPECT_TRUE(personal_data_->IsAutofillHasSeenIbanPrefEnabled());
@@ -1165,10 +1165,10 @@
   AddLocalIban(iban1);
   AddLocalIban(iban2);
   // Do not add `PersonalDataProfileTaskWaiter(*personal_data_).Wait()` for this
-  // `AddIban` operation, as it will be terminated prematurely for
+  // `AddAsLocalIban` operation, as it will be terminated prematurely for
   // `iban2_with_different_nickname` due to the presence of an IBAN with the
   // same value.
-  personal_data_->AddIban(iban2_with_different_nickname);
+  personal_data_->AddAsLocalIban(iban2_with_different_nickname);
 
   std::vector<Iban*> ibans = {&iban1, &iban2};
   ExpectSameElements(ibans, personal_data_->GetLocalIbans());
diff --git a/components/autofill/core/browser/test_autofill_external_delegate.cc b/components/autofill/core/browser/test_autofill_external_delegate.cc
index a359d04..71ce00f 100644
--- a/components/autofill/core/browser/test_autofill_external_delegate.cc
+++ b/components/autofill/core/browser/test_autofill_external_delegate.cc
@@ -106,6 +106,13 @@
   ASSERT_EQ(expected_num_suggestions, suggestions_.size());
 }
 
+void TestAutofillExternalDelegate::CheckSuggestionsNotReturned(
+    FieldGlobalId field_id) {
+  if (on_suggestions_returned_seen_) {
+    EXPECT_NE(field_id, field_id_);
+  }
+}
+
 void TestAutofillExternalDelegate::CheckNoSuggestions(FieldGlobalId field_id) {
   CheckSuggestions(field_id, 0, nullptr);
 }
@@ -120,6 +127,11 @@
   ASSERT_EQ(expected_num_suggestions, suggestions_.size());
 }
 
+const std::vector<Suggestion>& TestAutofillExternalDelegate::suggestions()
+    const {
+  return suggestions_;
+}
+
 bool TestAutofillExternalDelegate::on_query_seen() const {
   return on_query_seen_;
 }
diff --git a/components/autofill/core/browser/test_autofill_external_delegate.h b/components/autofill/core/browser/test_autofill_external_delegate.h
index 33b2038..14c359a 100644
--- a/components/autofill/core/browser/test_autofill_external_delegate.h
+++ b/components/autofill/core/browser/test_autofill_external_delegate.h
@@ -46,6 +46,9 @@
                         size_t expected_num_suggestions,
                         const Suggestion expected_suggestions[]);
 
+  // Check that the autofill suggestions were not sent at all.
+  void CheckSuggestionsNotReturned(FieldGlobalId field_id);
+
   // Check that the autofill suggestions were sent, and that they match a page
   // but contain no results.
   void CheckNoSuggestions(FieldGlobalId field_id);
@@ -55,6 +58,8 @@
   void CheckSuggestionCount(FieldGlobalId field_id,
                             size_t expected_num_suggestions);
 
+  const std::vector<Suggestion>& suggestions() const;
+
   bool on_query_seen() const;
 
   bool on_suggestions_returned_seen() const;
diff --git a/components/autofill/core/browser/test_personal_data_manager.cc b/components/autofill/core/browser/test_personal_data_manager.cc
index 249f308..8c241e5 100644
--- a/components/autofill/core/browser/test_personal_data_manager.cc
+++ b/components/autofill/core/browser/test_personal_data_manager.cc
@@ -102,7 +102,7 @@
   NotifyPersonalDataObserver();
 }
 
-std::string TestPersonalDataManager::AddIban(Iban iban) {
+std::string TestPersonalDataManager::AddAsLocalIban(Iban iban) {
   CHECK_EQ(iban.record_type(), Iban::kUnknown);
   iban.set_record_type(Iban::kLocalIban);
   iban.set_identifier(
diff --git a/components/autofill/core/browser/test_personal_data_manager.h b/components/autofill/core/browser/test_personal_data_manager.h
index cd8dfbfa6..ca299b8 100644
--- a/components/autofill/core/browser/test_personal_data_manager.h
+++ b/components/autofill/core/browser/test_personal_data_manager.h
@@ -52,7 +52,7 @@
   void RemoveByGUID(const std::string& guid) override;
   bool IsEligibleForAddressAccountStorage() const override;
   void AddCreditCard(const CreditCard& credit_card) override;
-  std::string AddIban(const Iban iban) override;
+  std::string AddAsLocalIban(const Iban iban) override;
   std::string UpdateIban(const Iban& iban) override;
   void DeleteLocalCreditCards(const std::vector<CreditCard>& cards) override;
   void UpdateCreditCard(const CreditCard& credit_card) override;
diff --git a/components/autofill/core/common/form_field_data.cc b/components/autofill/core/common/form_field_data.cc
index c319961..3a8ad01 100644
--- a/components/autofill/core/common/form_field_data.cc
+++ b/components/autofill/core/common/form_field_data.cc
@@ -394,16 +394,6 @@
   return properties_mask & kAutofilled;
 }
 
-std::u16string FormFieldData::GetSelection() const {
-  return std::u16string(GetSelectionAsStringView());
-}
-
-std::u16string_view FormFieldData::GetSelectionAsStringView() const {
-  size_t offset = std::min(static_cast<size_t>(selection_start), value.size());
-  size_t length = selection_end - selection_start;
-  return std::u16string_view(value).substr(offset, length);
-}
-
 // static
 bool FormFieldData::DeepEqual(const FormFieldData& a, const FormFieldData& b) {
   return a.unique_renderer_id == b.unique_renderer_id &&
diff --git a/components/autofill/core/common/form_field_data.h b/components/autofill/core/common/form_field_data.h
index e90a8362..6d4c73d 100644
--- a/components/autofill/core/common/form_field_data.h
+++ b/components/autofill/core/common/form_field_data.h
@@ -243,11 +243,6 @@
   bool HadFocus() const;
   bool WasPasswordAutofilled() const;
 
-  // Returns the currently selected text. Returns the empty string if
-  // `selection_start` and/or `selection_end` are out of bounds.
-  std::u16string GetSelection() const;
-  std::u16string_view GetSelectionAsStringView() const;
-
   // NOTE: Update `SameFieldAs()` and `FormFieldDataAndroid::SimilarFieldAs()`
   // if needed when adding new a member.
 
@@ -276,14 +271,6 @@
   // TODO(crbug.com/1501362): Extract on iOS.
   std::u16string selected_text;
 
-  // The range within `value` that is selected. `selection_start` points at the
-  // first selected character, `selection_end` points after the last selected
-  // character. That is, if nothing is selected, `selection_start` and
-  // `selection_end` are identical and represent the cursor position.
-  // Use GetSelection() or GetSelectionAsStringView() to safely get the selected
-  // substring of `value`.
-  uint32_t selection_start = 0;
-  uint32_t selection_end = 0;
   FormControlType form_control_type = FormControlType::kInputText;
   std::string autocomplete_attribute;
   absl::optional<AutocompleteParsingResult> parsed_autocomplete;
diff --git a/components/autofill/core/common/form_field_data_unittest.cc b/components/autofill/core/common/form_field_data_unittest.cc
index e14a743b4..c9f8c262 100644
--- a/components/autofill/core/common/form_field_data_unittest.cc
+++ b/components/autofill/core/common/form_field_data_unittest.cc
@@ -449,31 +449,4 @@
   }
 }
 
-class FormFieldDataGetSelectionTest
-    : public ::testing::TestWithParam<
-          std::tuple<size_t, size_t, const char16_t*>> {
- public:
-  size_t selection_start() const { return std::get<0>(GetParam()); }
-  size_t selection_end() const { return std::get<1>(GetParam()); }
-  std::u16string expectation() const { return std::get<2>(GetParam()); }
-};
-
-INSTANTIATE_TEST_SUITE_P(FormFieldDataTest,
-                         FormFieldDataGetSelectionTest,
-                         ::testing::Values(std::make_tuple(0u, 0u, u""),
-                                           std::make_tuple(0u, 3u, u"foo"),
-                                           std::make_tuple(3u, 6u, u"bar"),
-                                           std::make_tuple(6u, 6u, u""),
-                                           std::make_tuple(0u, 100, u"foobar"),
-                                           std::make_tuple(100u, 1000u, u"")));
-
-TEST_P(FormFieldDataGetSelectionTest, GetSelection) {
-  FormFieldData field;
-  field.value = u"foobar";
-  field.selection_start = selection_start();
-  field.selection_end = selection_end();
-  EXPECT_EQ(expectation(), field.GetSelection());
-  EXPECT_EQ(expectation(), field.GetSelectionAsStringView());
-}
-
 }  // namespace autofill
diff --git a/components/autofill/core/common/mojom/autofill_types.mojom b/components/autofill/core/common/mojom/autofill_types.mojom
index 44dde9b..521c4c6 100644
--- a/components/autofill/core/common/mojom/autofill_types.mojom
+++ b/components/autofill/core/common/mojom/autofill_types.mojom
@@ -350,8 +350,6 @@
   mojo_base.mojom.String16 name_attribute;
   mojo_base.mojom.String16 value;
   mojo_base.mojom.String16 selected_text;
-  uint32 selection_start;
-  uint32 selection_end;
   FormControlType form_control_type;
   string autocomplete_attribute;
   AutocompleteParsingResult? parsed_autocomplete;
diff --git a/components/autofill/core/common/mojom/autofill_types_mojom_traits.cc b/components/autofill/core/common/mojom/autofill_types_mojom_traits.cc
index 11d7503..2b09d20 100644
--- a/components/autofill/core/common/mojom/autofill_types_mojom_traits.cc
+++ b/components/autofill/core/common/mojom/autofill_types_mojom_traits.cc
@@ -180,10 +180,6 @@
   if (!data.ReadSelectedText(&out->selected_text)) {
     return false;
   }
-  uint32_t max_length = out->value.length();
-  out->selection_end = std::min(data.selection_end(), max_length);
-  out->selection_start = std::min(data.selection_start(), out->selection_end);
-  DCHECK_LE(out->selection_start, out->selection_end);
 
   if (!data.ReadFormControlType(&out->form_control_type))
     return false;
diff --git a/components/autofill/core/common/mojom/autofill_types_mojom_traits.h b/components/autofill/core/common/mojom/autofill_types_mojom_traits.h
index 7ae94c0..766a985 100644
--- a/components/autofill/core/common/mojom/autofill_types_mojom_traits.h
+++ b/components/autofill/core/common/mojom/autofill_types_mojom_traits.h
@@ -213,14 +213,6 @@
     return r.selected_text;
   }
 
-  static uint32_t selection_start(const autofill::FormFieldData& r) {
-    return r.selection_start;
-  }
-
-  static uint32_t selection_end(const autofill::FormFieldData& r) {
-    return r.selection_end;
-  }
-
   static autofill::mojom::FormControlType form_control_type(
       const autofill::FormFieldData& r) {
     return r.form_control_type;
diff --git a/components/autofill/core/common/mojom/autofill_types_mojom_traits_unittest.cc b/components/autofill/core/common/mojom/autofill_types_mojom_traits_unittest.cc
index d113ecf..10a0204 100644
--- a/components/autofill/core/common/mojom/autofill_types_mojom_traits_unittest.cc
+++ b/components/autofill/core/common/mojom/autofill_types_mojom_traits_unittest.cc
@@ -182,8 +182,6 @@
   EXPECT_TRUE(FormFieldData::DeepEqual(test::WithoutUnserializedData(expected),
                                        passed));
   EXPECT_EQ(expected.value, passed.value);
-  EXPECT_EQ(expected.selection_start, passed.selection_start);
-  EXPECT_EQ(expected.selection_end, passed.selection_end);
   EXPECT_EQ(expected.user_input, passed.user_input);
   std::move(closure).Run();
 }
@@ -298,8 +296,6 @@
   input.id_attribute = u"id";
   input.name_attribute = u"name";
   input.value = u"value";
-  input.selection_start = 1;
-  input.selection_end = 2;
   input.form_control_type = FormControlType::kInputText;
   input.autocomplete_attribute = "on";
   input.parsed_autocomplete =
diff --git a/components/browser_ui/site_settings/android/BUILD.gn b/components/browser_ui/site_settings/android/BUILD.gn
index dcd823379..e800328 100644
--- a/components/browser_ui/site_settings/android/BUILD.gn
+++ b/components/browser_ui/site_settings/android/BUILD.gn
@@ -92,6 +92,8 @@
     "java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsFeatureList.java",
     "java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsFeatureMap.java",
     "java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsUtil.java",
+    "java/src/org/chromium/components/browser_ui/site_settings/StorageAccessSubpageSettings.java",
+    "java/src/org/chromium/components/browser_ui/site_settings/StorageAccessWebsitePreference.java",
     "java/src/org/chromium/components/browser_ui/site_settings/StorageInfo.java",
     "java/src/org/chromium/components/browser_ui/site_settings/TriStateCookieSettingsPreference.java",
     "java/src/org/chromium/components/browser_ui/site_settings/TriStateSiteSettingsPreference.java",
@@ -259,6 +261,7 @@
     "java/res/xml/grouped_websites_preferences.xml",
     "java/res/xml/single_website_preferences.xml",
     "java/res/xml/site_settings_preferences.xml",
+    "java/res/xml/storage_access_settings.xml",
     "java/res/xml/website_preferences.xml",
   ]
 
diff --git a/components/browser_ui/site_settings/android/java/res/xml/storage_access_settings.xml b/components/browser_ui/site_settings/android/java/res/xml/storage_access_settings.xml
new file mode 100644
index 0000000..0b1a6bd
--- /dev/null
+++ b/components/browser_ui/site_settings/android/java/res/xml/storage_access_settings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+  <org.chromium.components.browser_ui.settings.TextMessagePreference
+      android:key="subtitle"
+      app:allowDividerBelow="false" />
+</PreferenceScreen>
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java
index 849341eb..fe21f55 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java
@@ -103,16 +103,20 @@
 
 /**
  * Shows a list of sites in a particular Site Settings category. For example, this could show all
- * the websites with microphone permissions. When the user selects a site, SingleWebsiteSettings
- * is launched to allow the user to see or modify the settings for that particular website.
+ * the websites with microphone permissions. When the user selects a site, SingleWebsiteSettings is
+ * launched to allow the user to see or modify the settings for that particular website.
  */
 @UsedByReflection("site_settings_preferences.xml")
 public class SingleCategorySettings extends BaseSiteSettingsFragment
-        implements OnPreferenceChangeListener, OnPreferenceClickListener, SiteAddedCallback,
-                   OnPreferenceTreeClickListener, FragmentSettingsLauncher,
-                   OnCookiesDetailsRequested,
-                   TriStateCookieSettingsPreference.OnCookiesDetailsRequested,
-                   CustomDividerFragment {
+        implements OnPreferenceChangeListener,
+                OnPreferenceClickListener,
+                SiteAddedCallback,
+                OnPreferenceTreeClickListener,
+                FragmentSettingsLauncher,
+                OnCookiesDetailsRequested,
+                TriStateCookieSettingsPreference.OnCookiesDetailsRequested,
+                CustomDividerFragment,
+                WebsitePreference.OnStorageAccessWebsiteDetailsRequested {
     @IntDef({GlobalToggleLayout.BINARY_TOGGLE, GlobalToggleLayout.TRI_STATE_TOGGLE,
             GlobalToggleLayout.TRI_STATE_COOKIE_TOGGLE,
             GlobalToggleLayout.FOUR_STATE_COOKIE_TOGGLE})
@@ -188,6 +192,18 @@
                 getActivity(), FPSCookieSettings.class, fragmentArgs);
     }
 
+    @Override
+    public void onStorageAccessWebsiteDetailsRequested(WebsitePreference website) {
+        Bundle fragmentArgs = new Bundle();
+        fragmentArgs.putSerializable(
+                StorageAccessSubpageSettings.EXTRA_STORAGE_ACCESS_STATE, website.site());
+        fragmentArgs.putBoolean(
+                StorageAccessSubpageSettings.EXTRA_ALLOWED, !isOnBlockList(website));
+
+        mSettingsLauncher.launchSettingsActivity(
+                getActivity(), StorageAccessSubpageSettings.class, fragmentArgs);
+    }
+
     // Note: these values must match the SiteLayout enum in enums.xml.
     @IntDef({SiteLayout.MOBILE, SiteLayout.DESKTOP})
     @Retention(RetentionPolicy.SOURCE)
@@ -540,6 +556,11 @@
                 return false;
             }
 
+            if (websitePreference.hasSubPage()) {
+                // TODO(http://b/307249155): Deletion for a group of storage access.
+                return false;
+            }
+
             if (websitePreference.getParent().getKey().equals(MANAGED_GROUP)) {
                 websitePreference.setFragment(SingleWebsiteSettings.class.getName());
                 websitePreference.putSiteAddressIntoExtras(
@@ -934,6 +955,10 @@
             if (mSearch == null || mSearch.isEmpty() || site.getTitle().contains(mSearch)) {
                 WebsitePreference preference = new WebsitePreference(
                         getStyledContext(), getSiteSettingsDelegate(), site, mCategory);
+
+                if (mCategory.getType() == SiteSettingsCategory.Type.STORAGE_ACCESS) {
+                    preference.setStorageAccessSettingsPageListener(this);
+                }
                 websites.add(preference);
                 preference.setManagedPreferenceDelegate(websiteDelegate);
             }
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/StorageAccessSubpageSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/StorageAccessSubpageSettings.java
new file mode 100644
index 0000000..313b8ac1
--- /dev/null
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/StorageAccessSubpageSettings.java
@@ -0,0 +1,107 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.browser_ui.site_settings;
+
+import android.os.Bundle;
+
+import androidx.preference.PreferenceScreen;
+
+import org.chromium.components.browser_ui.settings.CustomDividerFragment;
+import org.chromium.components.browser_ui.settings.SettingsUtils;
+import org.chromium.components.browser_ui.settings.TextMessagePreference;
+import org.chromium.components.content_settings.ContentSettingsType;
+
+import java.util.List;
+
+/**
+ * Shows a list of Storage Access permissions grouped by their origin and of the same type, that is,
+ * if they are allowed or blocked. This fragment is opened on top of {@link SingleCategorySettings}.
+ */
+public class StorageAccessSubpageSettings extends BaseSiteSettingsFragment
+        implements CustomDividerFragment,
+                StorageAccessWebsitePreference.OnStorageAccessWebsiteReset {
+    public static final String SUBTITLE_KEY = "subtitle";
+
+    public static final String EXTRA_STORAGE_ACCESS_STATE = "extra_storage_access_state";
+    public static final String EXTRA_ALLOWED = "allowed";
+
+    private Website mSite;
+    private Boolean mIsAllowed;
+    private TextMessagePreference mSubtitle;
+
+    @Override
+    public boolean hasDivider() {
+        return false;
+    }
+
+    @Override
+    public void onCreatePreferences(Bundle bundle, String s) {
+        resetList();
+
+        Object extraSite = getArguments().getSerializable(EXTRA_STORAGE_ACCESS_STATE);
+        assert extraSite != null;
+        mSite = (Website) extraSite;
+        getActivity().setTitle(mSite.getTitleForPreferenceRow());
+
+        mIsAllowed = getArguments().getBoolean(StorageAccessSubpageSettings.EXTRA_ALLOWED);
+        mSubtitle = (TextMessagePreference) findPreference(SUBTITLE_KEY);
+
+        mSubtitle.setTitle(
+                getContext()
+                        .getString(
+                                mIsAllowed
+                                        ? R.string.website_settings_storage_access_allowed_subtitle
+                                        : R.string.website_settings_storage_access_blocked_subtitle,
+                                mSite.getTitleForPreferenceRow()));
+
+        updateEmbeddedSites();
+    }
+
+    private void resetList() {
+        PreferenceScreen screen = getPreferenceScreen();
+        if (screen != null) {
+            screen.removeAll();
+        }
+        SettingsUtils.addPreferencesFromResource(this, R.xml.storage_access_settings);
+    }
+
+    private void updateEmbeddedSites() {
+        PreferenceScreen screen = getPreferenceScreen();
+
+        List<ContentSettingException> exceptions =
+                mSite.getEmbeddedContentSettings(ContentSettingsType.STORAGE_ACCESS);
+        for (ContentSettingException exception : exceptions) {
+            WebsiteAddress permissionOrigin = WebsiteAddress.create(exception.getPrimaryPattern());
+            WebsiteAddress permissionEmbedder =
+                    WebsiteAddress.create(exception.getSecondaryPattern());
+            Website site = new Website(permissionOrigin, permissionEmbedder);
+            site.addEmbeddedPermission(exception);
+            StorageAccessWebsitePreference preference =
+                    new StorageAccessWebsitePreference(
+                            screen.getContext(), getSiteSettingsDelegate(), site, this);
+            screen.addPreference(preference);
+        }
+    }
+
+    @Override
+    public void onStorageAccessWebsiteReset(StorageAccessWebsitePreference preference) {
+        getPreferenceScreen().removePreference(preference);
+
+        List<ContentSettingException> exceptions =
+                mSite.getEmbeddedContentSettings(ContentSettingsType.STORAGE_ACCESS);
+        ContentSettingException exception =
+                preference
+                        .site()
+                        .getEmbeddedContentSettings(ContentSettingsType.STORAGE_ACCESS)
+                        .get(0);
+        exceptions.remove(exception);
+
+        if (exceptions.isEmpty()) {
+            // Return to parent fragment if there are no embedded exceptions.
+            getActivity().finish();
+            return;
+        }
+    }
+}
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/StorageAccessWebsitePreference.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/StorageAccessWebsitePreference.java
new file mode 100644
index 0000000..4a0c9e3
--- /dev/null
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/StorageAccessWebsitePreference.java
@@ -0,0 +1,93 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.browser_ui.site_settings;
+
+import android.content.Context;
+import android.view.View.OnClickListener;
+
+import org.chromium.components.content_settings.ContentSettingValues;
+import org.chromium.components.content_settings.ContentSettingsType;
+
+import java.util.List;
+
+/**
+ * A preference for embedded Storage Access permission that displays the embedder website's favicon
+ * and URL, and an icon on the RHS to reset the permission. See {@link WebsitePreference} for more
+ * details on how this preference can be used.
+ */
+class StorageAccessWebsitePreference extends WebsitePreference {
+
+    private final OnStorageAccessWebsiteReset mOnStorageAccessWebsiteResetListener;
+
+    /** Used to notify storage access embedded website permission reset requests. */
+    public interface OnStorageAccessWebsiteReset {
+        /** Notify that the embedded website permission has been reset. */
+        void onStorageAccessWebsiteReset(StorageAccessWebsitePreference preference);
+    }
+
+    StorageAccessWebsitePreference(
+            Context context,
+            SiteSettingsDelegate siteSettingsClient,
+            Website site,
+            OnStorageAccessWebsiteReset onStorageAccessWebsiteResetListener) {
+        super(
+                context,
+                siteSettingsClient,
+                site,
+                SiteSettingsCategory.createFromType(
+                        siteSettingsClient.getBrowserContextHandle(),
+                        SiteSettingsCategory.Type.STORAGE_ACCESS));
+
+        mOnStorageAccessWebsiteResetListener = onStorageAccessWebsiteResetListener;
+    }
+
+    @Override
+    protected String buildTitle() {
+        return mSite.getTitleForEmbeddedPreferenceRow();
+    }
+
+    @Override
+    protected String buildSummary() {
+
+        List<ContentSettingException> exceptions =
+                mSite.getEmbeddedContentSettings(ContentSettingsType.STORAGE_ACCESS);
+        assert exceptions.size() == 1;
+
+        ContentSettingException exception = exceptions.get(0);
+        if (exception.isEmbargoed()) {
+            return getContext().getString(R.string.automatically_blocked);
+        }
+
+        if (exception != null && exception.hasExpiration()) {
+            return buildExpirationSummary(exception);
+        }
+
+        return null;
+    }
+
+    @Override
+    protected void maybeSetImageView() {
+        setImageView(
+                R.drawable.ic_delete_white_24dp,
+                R.string.webstorage_delete_data_dialog_title,
+                (OnClickListener)
+                        view -> {
+                            mSite.setContentSetting(
+                                    mSiteSettingsDelegate.getBrowserContextHandle(),
+                                    mCategory.getContentSettingsType(),
+                                    ContentSettingValues.DEFAULT);
+                            mOnStorageAccessWebsiteResetListener.onStorageAccessWebsiteReset(this);
+                        });
+        setImageViewEnabled(true);
+        setImagePadding(25, 0, 0, 0);
+    }
+
+    @Override
+    protected void refresh() {
+        assert mCategory.getType() == SiteSettingsCategory.Type.STORAGE_ACCESS;
+        setSelectable(false);
+        super.refresh();
+    }
+}
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java
index 0ca6d9c..ed551e5 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java
@@ -205,18 +205,47 @@
     }
 
     /**
+     * Returns the ContentSettingValue associated with the list of embedded exceptions associated
+     * with the website. Each website object should only have embedded exceptions of a single
+     * ContentSettingValue.
+     *
+     * @param exceptions Website embedded exceptions.
+     * @return the ContentSettingValue of the list of |exceptions|.
+     */
+    private @ContentSettingValues @Nullable Integer getContentSetting(
+            List<ContentSettingException> exceptions) {
+        assert !exceptions.isEmpty();
+
+        @ContentSettingValues
+        @Nullable
+        Integer contentSetting = exceptions.get(0).getContentSetting();
+
+        for (ContentSettingException exception : exceptions) {
+            assert exception.getContentSetting().equals(contentSetting);
+        }
+
+        return contentSetting;
+    }
+
+    public List<ContentSettingException> getEmbeddedContentSettings(@ContentSettingsType int type) {
+        assert isEmbeddedPermission(type);
+        return getEmbeddedPermissions().get(type);
+    }
+
+    /**
      * @return ContentSettingValue for specified ContentSettingsType.
      *         (Camera, Clipboard, etc.).
      */
     public @ContentSettingValues @Nullable Integer getContentSetting(
             BrowserContextHandle browserContextHandle, @ContentSettingsType int type) {
         if (isEmbeddedPermission(type)) {
-            var exceptions = getEmbeddedPermissions().get(type);
-            if (exceptions == null) return null;
-            // This function may not be used when multiple embedded permissions have been merged
-            // into one website like for SingleWebsiteSettings.
-            assert exceptions.size() == 1;
-            return exceptions.get(0).getContentSetting();
+            var exceptions = getEmbeddedContentSettings(type);
+            if (exceptions == null || exceptions.isEmpty()) return null;
+
+            // If multiple embedded permissions have been merged into one website like for
+            // SingleWebsiteSettings, this function may only be used if all permission exceptions
+            // have been grouped by content setting.
+            return getContentSetting(exceptions);
         } else if (getPermissionInfo(type) != null) {
             return getPermissionInfo(type).getContentSetting(browserContextHandle);
         } else if (getContentSettingException(type) != null) {
@@ -297,6 +326,13 @@
      * is embargoed.
      */
     public boolean isEmbargoed(@ContentSettingsType int type) {
+        if (isEmbeddedPermission(type)) {
+            List<ContentSettingException> exceptions = getEmbeddedContentSettings(type);
+            assert exceptions.size() == 1;
+
+            return exceptions.get(0).isEmbargoed();
+        }
+
         PermissionInfo permissionInfo = getPermissionInfo(type);
         if (permissionInfo != null && permissionInfo.isEmbargoed()) return true;
 
@@ -397,6 +433,10 @@
         return new ArrayList<ChosenObjectInfo>(mObjectInfo);
     }
 
+    public String getTitleForEmbeddedPreferenceRow() {
+        return omitProtocolIfPresent(mEmbedder.getTitle());
+    }
+
     // WebsiteEntry implementation.
     @Override
     public String getTitleForPreferenceRow() {
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
index dc70903a..1e4ea93 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
@@ -10,9 +10,11 @@
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import org.chromium.base.Callback;
 import org.chromium.base.CommandLine;
+import org.chromium.components.content_settings.ContentSettingValues;
 import org.chromium.components.content_settings.ContentSettingsType;
 import org.chromium.components.permissions.PermissionsAndroidFeatureList;
 import org.chromium.components.permissions.PermissionsAndroidFeatureMap;
@@ -202,8 +204,9 @@
      * the permissions that the user has set for them.
      */
     private class WebsitePermissionFetcherInternal {
-        // This map looks up Websites by their origin and embedder.
-        private final Map<OriginAndEmbedder, Website> mSites = new HashMap<>();
+        // This map looks up Websites by their origin and embedder and content setting (e.g. allow,
+        // block).
+        private final Map<Pair<OriginAndEmbedder, Integer>, Website> mSites = new HashMap<>();
 
         /**
          * Fetches preferences for all sites that have them. TODO(mvanouwerkerk): Add an argument
@@ -352,6 +355,13 @@
         }
 
         private Website findOrCreateSite(String origin, String embedder) {
+            return findOrCreateSite(origin, embedder, null);
+        }
+
+        private Website findOrCreateSite(
+                String origin,
+                String embedder,
+                @ContentSettingValues @Nullable Integer contentSetting) {
             // Ensure that the origin parameter is actually an origin or a wildcard.
             // The purpose of the check is to prevent duplicate entries in the list when getting a
             // mix of origins and hosts. Except, in the case of the Zoom category, where we want to
@@ -375,7 +385,10 @@
             WebsiteAddress permissionOrigin = WebsiteAddress.create(origin);
             WebsiteAddress permissionEmbedder = WebsiteAddress.create(embedder);
 
-            OriginAndEmbedder key = OriginAndEmbedder.create(permissionOrigin, permissionEmbedder);
+            Pair<OriginAndEmbedder, Integer> key =
+                    new Pair<>(
+                            OriginAndEmbedder.create(permissionOrigin, permissionEmbedder),
+                            contentSetting);
 
             Website site = mSites.get(key);
             if (site == null) {
@@ -393,6 +406,9 @@
                             mBrowserContextHandle, contentSettingsType)) {
                 String address = exception.getPrimaryPattern();
                 String embedder = exception.getSecondaryPattern();
+                @ContentSettingValues
+                @Nullable
+                Integer contentSetting = null;
 
                 if (isEmbeddedPermission
                         && embedder != null
@@ -402,6 +418,12 @@
                     // AllSites should group embedded permissions by embedder.
                     address = embedder;
                     embedder = SITE_WILDCARD;
+                } else if (isEmbeddedPermission
+                        && mSiteSettingsCategory != null
+                        && mSiteSettingsCategory.getType()
+                                == SiteSettingsCategory.Type.STORAGE_ACCESS) {
+                    embedder = SITE_WILDCARD;
+                    contentSetting = exception.getContentSetting();
                 }
 
                 // If both patterns are the wildcard, dont display this rule.
@@ -413,7 +435,7 @@
                 String origin = containsPatternWildcards(address)
                         ? address
                         : WebsiteAddress.create(address).getOrigin();
-                Website site = findOrCreateSite(origin, embedder);
+                Website site = findOrCreateSite(origin, embedder, contentSetting);
                 if (isEmbeddedPermission) {
                     site.addEmbeddedPermission(exception);
                 } else {
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java
index 3fd1afa..abaaab1 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java
@@ -4,6 +4,8 @@
 
 package org.chromium.components.browser_ui.site_settings;
 
+import static org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge.SITE_WILDCARD;
+
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
@@ -31,9 +33,9 @@
  * can be used.
  */
 class WebsitePreference extends ChromeImageViewPreference {
-    private final SiteSettingsDelegate mSiteSettingsDelegate;
-    private final Website mSite;
-    private final SiteSettingsCategory mCategory;
+    protected final SiteSettingsDelegate mSiteSettingsDelegate;
+    protected final Website mSite;
+    protected final SiteSettingsCategory mCategory;
     private Runnable mRefreshZoomsListFunction;
 
     // TODO(crbug.com/1076571): Move these constants to dimens.xml
@@ -42,6 +44,14 @@
     // Whether the favicon has been fetched already.
     private boolean mFaviconFetched;
 
+    private OnStorageAccessWebsiteDetailsRequested mStorageAccessSettingsPageListener;
+
+    /** Used to notify storage access website details subpage requests. */
+    public interface OnStorageAccessWebsiteDetailsRequested {
+        /** Notify that website details subpage is requested. */
+        void onStorageAccessWebsiteDetailsRequested(WebsitePreference website);
+    }
+
     WebsitePreference(Context context, SiteSettingsDelegate siteSettingsClient, Website site,
             SiteSettingsCategory category) {
         super(context);
@@ -64,6 +74,11 @@
         mRefreshZoomsListFunction = refreshZoomsListCallback;
     }
 
+    public void setStorageAccessSettingsPageListener(
+            OnStorageAccessWebsiteDetailsRequested storageAccessSettingsPageListener) {
+        mStorageAccessSettingsPageListener = storageAccessSettingsPageListener;
+    }
+
     public void putSiteIntoExtras(String key) {
         getExtras().putSerializable(key, mSite);
     }
@@ -90,68 +105,138 @@
         return UrlUtilities.clearPort(uri);
     }
 
-    private void refresh() {
-        setTitle(mSite.getTitle());
+    /**
+     * @return if a |mCategory| has a sub page to show the |mSite| permissions.
+     */
+    public boolean hasSubPage() {
+        int type = mCategory.getContentSettingsType();
 
-        if (mCategory.getType() == SiteSettingsCategory.Type.ZOOM) {
-            // Create and set the delete button for this preference.
-            setImageView(R.drawable.btn_close, R.string.webstorage_delete_data_dialog_title,
-                    (OnClickListener) view -> {
-                        SiteSettingsUtil.resetZoomLevel(
-                                mSite, mSiteSettingsDelegate.getBrowserContextHandle());
-                        mRefreshZoomsListFunction.run();
-                    });
-            setImageViewEnabled(true);
-            setImagePadding(25, 0, 0, 0);
+        if (!Website.isEmbeddedPermission(type)) {
+            return false;
         }
 
+        int numberSites = mSite.getEmbeddedContentSettings(type).size();
+        return numberSites != 1 || !mSite.isEmbargoed(type);
+    }
+
+    protected String buildTitle() {
+        if (mCategory.getType() == SiteSettingsCategory.Type.STORAGE_ACCESS) {
+            return mSite.getTitleForPreferenceRow();
+        }
+
+        return mSite.getTitle();
+    }
+
+    protected String buildExpirationSummary(ContentSettingException exception) {
+        assert exception != null && exception.hasExpiration();
+        var expirationInDays = exception.getExpirationInDays();
+        return expirationInDays == 0
+                ? getContext().getString(R.string.site_settings_expires_today_label)
+                : getContext()
+                        .getResources()
+                        .getQuantityString(
+                                R.plurals.site_settings_expires_label,
+                                expirationInDays,
+                                expirationInDays);
+    }
+
+    protected String buildSummary() {
         if (mSiteSettingsDelegate.isPrivacySandboxFirstPartySetsUIFeatureEnabled()
                 && mSiteSettingsDelegate.isFirstPartySetsDataAccessEnabled()
                 && mSite.getFPSCookieInfo() != null) {
             var fpsInfo = mSite.getFPSCookieInfo();
-            setSummary(getContext().getResources().getQuantityString(
-                    R.plurals.allsites_fps_list_summary, fpsInfo.getMembersCount(),
-                    Integer.toString(fpsInfo.getMembersCount()), fpsInfo.getOwner()));
-            return;
+            return getContext()
+                    .getResources()
+                    .getQuantityString(
+                            R.plurals.allsites_fps_list_summary,
+                            fpsInfo.getMembersCount(),
+                            Integer.toString(fpsInfo.getMembersCount()),
+                            fpsInfo.getOwner());
+        }
+
+        if (hasSubPage()) {
+            int numberSites =
+                    mSite.getEmbeddedContentSettings(mCategory.getContentSettingsType()).size();
+            return getContext()
+                    .getResources()
+                    .getQuantityString(
+                            R.plurals.number_sites, numberSites, Integer.toString(numberSites));
         }
 
         if (mSite.getEmbedder() == null) {
             if (mSite.isEmbargoed(mCategory.getContentSettingsType())) {
-                setSummary(getContext().getString(R.string.automatically_blocked));
-            } else if (mCategory.getType() == SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE
-                    && mSite.getAddress().getIsAnySubdomainPattern()) {
-                setSummary(String.format(
-                        getContext().getString(R.string.website_settings_domain_exception_label),
-                        mSite.getAddress().getHost()));
+                return getContext().getString(R.string.automatically_blocked);
             }
-            return;
-        }
 
-        if (Website.isEmbeddedPermission(mCategory.getContentSettingsType())) {
-            String subtitleText;
-            if (mSite.representsThirdPartiesOnSite()) {
-                subtitleText = getContext().getString(
-                        R.string.website_settings_third_party_cookies_exception_label);
-            } else {
-                subtitleText =
-                        String.format(getContext().getString(R.string.website_settings_embedded_on),
-                                mSite.getEmbedder().getTitle());
+            if (mCategory.getType() == SiteSettingsCategory.Type.REQUEST_DESKTOP_SITE
+                    && mSite.getAddress().getIsAnySubdomainPattern()) {
+                return String.format(
+                        getContext().getString(R.string.website_settings_domain_exception_label),
+                        mSite.getAddress().getHost());
             }
-            setSummary(subtitleText);
+
+            return null;
         }
 
         if (mSiteSettingsDelegate.isUserBypassUIEnabled()
                 && mCategory.getType() == SiteSettingsCategory.Type.THIRD_PARTY_COOKIES) {
             var exception = mSite.getContentSettingException(ContentSettingsType.COOKIES);
             if (exception != null && exception.hasExpiration()) {
-                var expirationInDays = exception.getExpirationInDays();
-                setSummary(expirationInDays == 0
-                                ? getContext().getString(R.string.site_settings_expires_today_label)
-                                : getContext().getResources().getQuantityString(
-                                        R.plurals.site_settings_expires_label, expirationInDays,
-                                        expirationInDays));
+                return buildExpirationSummary(exception);
             }
+            return null;
         }
+
+        String embedderTitle = mSite.getEmbedder().getTitle();
+        if (mSite.representsThirdPartiesOnSite()
+                || (embedderTitle != null
+                        && (embedderTitle.isEmpty() || embedderTitle.equals(SITE_WILDCARD)))) {
+            return null;
+        }
+
+        // TODO(crbug.com/1478113): Check if on Android there is a possibility of other exceptions
+        // being scoped to an embedder.
+        return String.format(
+                getContext().getString(R.string.website_settings_embedded_on),
+                mSite.getEmbedder().getTitle());
+    }
+
+    protected void maybeSetImageView() {
+        if (mCategory.getType() == SiteSettingsCategory.Type.ZOOM) {
+            // Create and set the delete button for this preference.
+            setImageView(
+                    R.drawable.btn_close,
+                    R.string.webstorage_delete_data_dialog_title,
+                    (OnClickListener)
+                            view -> {
+                                SiteSettingsUtil.resetZoomLevel(
+                                        mSite, mSiteSettingsDelegate.getBrowserContextHandle());
+                                mRefreshZoomsListFunction.run();
+                            });
+            setImageViewEnabled(true);
+            setImagePadding(25, 0, 0, 0);
+            return;
+        }
+
+        if (hasSubPage()) {
+            setImageView(
+                    R.drawable.ic_expand_more_horizontal_black_24dp,
+                    R.string.webstorage_delete_data_dialog_title,
+                    (OnClickListener)
+                            view -> {
+                                mStorageAccessSettingsPageListener
+                                        .onStorageAccessWebsiteDetailsRequested(this);
+                            });
+            setImageViewEnabled(true);
+            setImagePadding(25, 0, 0, 0);
+            return;
+        }
+    }
+
+    protected void refresh() {
+        setTitle(buildTitle());
+        maybeSetImageView();
+        setSummary(buildSummary());
     }
 
     @Override
diff --git a/components/browser_ui/strings/android/site_settings.grdp b/components/browser_ui/strings/android/site_settings.grdp
index 2d5e2be4..1f4eeee 100644
--- a/components/browser_ui/strings/android/site_settings.grdp
+++ b/components/browser_ui/strings/android/site_settings.grdp
@@ -202,7 +202,7 @@
     Turn on permissions for <ph name="APP_NAME">%1$s<ex>Chrome</ex></ph> in <ph name="BEGIN_LINK">&lt;link&gt;</ph>Android Settings<ph name="END_LINK">&lt;/link&gt;</ph>.
   </message>
   <message name="IDS_WEBSITE_SETTINGS_EMBEDDED_ON" desc="String used to indicate the embedder origin of a site">
-    Embedded on <ph name="WEBSITE_URL">%1$s<ex>google.com</ex></ph>
+      Embedded on <ph name="WEBSITE_URL">%1$s<ex>google.com</ex></ph>
   </message>
   <message name="IDS_CLEAR_BROWSING_DATA_LINK" desc="Title of the button that will open the clear browsing data dialog.">
     Clear browsing data…
@@ -935,4 +935,15 @@
   <message name="IDS_WEBSITE_SETTINGS_STORAGE_ACCESS_PAGE_DESCRIPTION" desc="The body description on the Embedded content settings page.">
     Sites you visit can embed content from other sites, for example images, ads, and text. These other sites can ask for permission to use info they've saved about you as you browse the site. <ph name="BEGIN_LINK">&lt;link&gt;</ph>Learn more about embedded content<ph name="END_LINK">&lt;/link&gt;</ph>
   </message>
+  <message name="IDS_WEBSITE_SETTINGS_STORAGE_ACCESS_ALLOWED_SUBTITLE" desc="Text that explains that a webite is allowed to “use their info“ on one or more sites that follow this text. Previously, the user 1) visited a site, such as www.myfavoritesite.com 2)  www.myfavoritesite.com embeds content and services, such as Google Docs. 3) Google Docs prompted the user for permission to use data Google Docs has previously saved related to the user. 4) The user says yes. 5) Later in settings, the user sees the site exception with this text and can revoke its access.">
+    <ph name="SITE">%1$s<ex>google.com</ex></ph> can use your info as you browse
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_STORAGE_ACCESS_BLOCKED_SUBTITLE" desc="Text that explains that a webite is not allowed to “use their info“ on one or more sites that follow this text. Previously, the user 1) visited a site, such as www.myfavoritesite.com 2)  www.myfavoritesite.com embeds content and services, such as Google Docs. 3) Google Docs prompted the user for permission to use data Google Docs has previously saved related to the user. 4) The user says yes. 5) Later in settings, the user sees the site exception with this text and can unblock the site.">
+    <ph name="SITE">%1$s<ex>google.com</ex></ph> is blocked from using your info on
+  </message>
+  <message name="IDS_NUMBER_SITES" desc="Number of embedded sites associated with a permission">
+    {COUNT, plural,
+      =1 {<ph name="SITE_COUNT">%1$s<ex>1</ex></ph> site}
+      other {<ph name="SITE_COUNT">%1$s<ex>9</ex></ph> sites}}
+  </message>
 </grit-part>
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_NUMBER_SITES.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_NUMBER_SITES.png.sha1
new file mode 100644
index 0000000..af56161
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_NUMBER_SITES.png.sha1
@@ -0,0 +1 @@
+c8c97ead83af2c9efdf7e57d1d8836bc7b5b72e5
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_STORAGE_ACCESS_ALLOWED_SUBTITLE.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_STORAGE_ACCESS_ALLOWED_SUBTITLE.png.sha1
new file mode 100644
index 0000000..d80ff48
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_STORAGE_ACCESS_ALLOWED_SUBTITLE.png.sha1
@@ -0,0 +1 @@
+de5c61af40c87e46a86f60034f42ad89ea2994cc
\ No newline at end of file
diff --git a/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_STORAGE_ACCESS_BLOCKED_SUBTITLE.png.sha1 b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_STORAGE_ACCESS_BLOCKED_SUBTITLE.png.sha1
new file mode 100644
index 0000000..d28238a
--- /dev/null
+++ b/components/browser_ui/strings/android/site_settings_grdp/IDS_WEBSITE_SETTINGS_STORAGE_ACCESS_BLOCKED_SUBTITLE.png.sha1
@@ -0,0 +1 @@
+7f8345b48e8015b3ac89f7db25a99c4825b620e6
\ No newline at end of file
diff --git a/components/browsing_data/content/browsing_data_model.cc b/components/browsing_data/content/browsing_data_model.cc
index 87c1665..1242c1a 100644
--- a/components/browsing_data/content/browsing_data_model.cc
+++ b/components/browsing_data/content/browsing_data_model.cc
@@ -338,6 +338,12 @@
 void StorageRemoverHelper::Visitor::operator()<net::CanonicalCookie>(
     const net::CanonicalCookie& cookie) {
   if (types.Has(BrowsingDataModel::StorageType::kCookie)) {
+    if (helper->delegate_ && helper->delegate_->IsCookieDeletionDisabled(
+                                 net::cookie_util::CookieOriginToURL(
+                                     cookie.Domain(), cookie.IsSecure()))) {
+      // TODO(crbug.com/1500256): Expand test coverage for this block.
+      return;
+    }
     helper->storage_partition_->GetCookieManagerForBrowserProcess()
         ->DeleteCanonicalCookie(
             cookie,
diff --git a/components/browsing_data/content/browsing_data_model.h b/components/browsing_data/content/browsing_data_model.h
index c65ec14..19b66ed 100644
--- a/components/browsing_data/content/browsing_data_model.h
+++ b/components/browsing_data/content/browsing_data_model.h
@@ -166,6 +166,9 @@
     virtual absl::optional<bool> IsBlockedByThirdPartyCookieBlocking(
         StorageType storage_type) const = 0;
 
+    // Returns whether cookie deletion for a given `url` is disabled.
+    virtual bool IsCookieDeletionDisabled(const GURL& url) = 0;
+
     virtual ~Delegate() = default;
   };
 
diff --git a/components/browsing_data/content/browsing_data_model_unittest.cc b/components/browsing_data/content/browsing_data_model_unittest.cc
index 3da550c1..6cf3bd8 100644
--- a/components/browsing_data/content/browsing_data_model_unittest.cc
+++ b/components/browsing_data/content/browsing_data_model_unittest.cc
@@ -381,6 +381,8 @@
     return false;
   }
 
+  bool IsCookieDeletionDisabled(const GURL& url) override { return false; }
+
  private:
   std::string origin_owned_host_;
 };
diff --git a/components/browsing_data/content/test_browsing_data_model_delegate.cc b/components/browsing_data/content/test_browsing_data_model_delegate.cc
index 801f8a4d..bbb7dfc25 100644
--- a/components/browsing_data/content/test_browsing_data_model_delegate.cc
+++ b/components/browsing_data/content/test_browsing_data_model_delegate.cc
@@ -60,4 +60,8 @@
   }
 }
 
+bool TestBrowsingDataModelDelegate::IsCookieDeletionDisabled(const GURL& url) {
+  return false;
+}
+
 }  // namespace browsing_data
diff --git a/components/browsing_data/content/test_browsing_data_model_delegate.h b/components/browsing_data/content/test_browsing_data_model_delegate.h
index 549bee9..0346dd3 100644
--- a/components/browsing_data/content/test_browsing_data_model_delegate.h
+++ b/components/browsing_data/content/test_browsing_data_model_delegate.h
@@ -30,6 +30,7 @@
       BrowsingDataModel::StorageType storage_type) const override;
   absl::optional<bool> IsBlockedByThirdPartyCookieBlocking(
       BrowsingDataModel::StorageType storage_type) const override;
+  bool IsCookieDeletionDisabled(const GURL& url) override;
 
  private:
   std::map<BrowsingDataModel::DataKey, BrowsingDataModel::StorageTypeSet>
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index bd01a436..9b0a68be 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@
 {
-  "version": "26.45",
-  "log_list_timestamp": "2023-11-16T12:58:27Z",
+  "version": "26.46",
+  "log_list_timestamp": "2023-11-17T12:54:53Z",
   "operators": [
     {
       "name": "Google",
diff --git a/components/password_manager/core/browser/password_form_metrics_recorder.cc b/components/password_manager/core/browser/password_form_metrics_recorder.cc
index 0f83c3b..89005c0 100644
--- a/components/password_manager/core/browser/password_form_metrics_recorder.cc
+++ b/components/password_manager/core/browser/password_form_metrics_recorder.cc
@@ -727,6 +727,7 @@
     case metrics_util::AUTOMATIC_SHARED_PASSWORDS_NOTIFICATION:
     case metrics_util::AUTOMATIC_ADD_USERNAME_BUBBLE:
     case metrics_util::MANUAL_ADD_USERNAME_BUBBLE:
+    case metrics_util::AUTOMATIC_RELAUNCH_CHROME_BUBBLE:
       // Do nothing.
       return;
 
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc
index ba5c69d..f7e4d4a 100644
--- a/components/password_manager/core/browser/password_manager.cc
+++ b/components/password_manager/core/browser/password_manager.cc
@@ -59,6 +59,10 @@
 #include "components/prefs/pref_registry_simple.h"
 #endif  // BUILDFLAG(IS_WIN)
 
+#if BUILDFLAG(IS_MAC)
+#include "components/os_crypt/sync/os_crypt.h"
+#endif
+
 using autofill::ACCOUNT_CREATION_PASSWORD;
 using autofill::CalculateFormSignature;
 using autofill::FieldDataManager;
@@ -336,9 +340,6 @@
   registry->RegisterIntegerPref(
       prefs::kPasswordGenerationBottomSheetDismissCount, 0);
 #endif  // BUILDFLAG(IS_ANDROID)
-  // Preferences for |PasswordChangeSuccessTracker|.
-  registry->RegisterIntegerPref(prefs::kPasswordChangeSuccessTrackerVersion, 0);
-  registry->RegisterListPref(prefs::kPasswordChangeSuccessTrackerFlows);
 
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
   registry->RegisterIntegerPref(
@@ -357,6 +358,10 @@
   registry->RegisterListPref(prefs::kPasswordManagerPromoCardsList);
 #endif  // BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
   registry->RegisterBooleanPref(prefs::kPasswordSharingEnabled, true);
+#if BUILDFLAG(IS_MAC)
+  registry->RegisterIntegerPref(prefs::kRelaunchChromeBubbleDismissedCounter,
+                                0);
+#endif
 }
 
 // static
diff --git a/components/password_manager/core/browser/password_manager_metrics_util.h b/components/password_manager/core/browser/password_manager_metrics_util.h
index 1aa11498..d093f676 100644
--- a/components/password_manager/core/browser/password_manager_metrics_util.h
+++ b/components/password_manager/core/browser/password_manager_metrics_util.h
@@ -49,6 +49,7 @@
   AUTOMATIC_SHARED_PASSWORDS_NOTIFICATION = 16,
   AUTOMATIC_ADD_USERNAME_BUBBLE = 17,
   MANUAL_ADD_USERNAME_BUBBLE = 18,
+  AUTOMATIC_RELAUNCH_CHROME_BUBBLE = 19,
   NUM_DISPLAY_DISPOSITIONS,
 };
 
diff --git a/components/password_manager/core/common/password_manager_pref_names.cc b/components/password_manager/core/common/password_manager_pref_names.cc
index b700d06..2b9789e 100644
--- a/components/password_manager/core/common/password_manager_pref_names.cc
+++ b/components/password_manager/core/common/password_manager_pref_names.cc
@@ -120,11 +120,6 @@
 const char kAccountStoreDateLastUsedForFilling[] =
     "password_manager.account_store_date_last_used_for_filling";
 
-const char kPasswordChangeSuccessTrackerFlows[] =
-    "password_manager.password_change_success_tracker.flows";
-const char kPasswordChangeSuccessTrackerVersion[] =
-    "password_manager.password_change_success_tracker.version";
-
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
 const char kBiometricAuthBeforeFillingPromoShownCounter[] =
     "password_manager.biometric_authentication_filling_promo_counter";
@@ -152,5 +147,10 @@
 const char kPasswordSharingEnabled[] =
     "password_manager.password_sharing_enabled";
 
+#if BUILDFLAG(IS_MAC)
+const char kRelaunchChromeBubbleDismissedCounter[] =
+    "password_manager.relaunch_chrome_bubble_dismissed_counter";
+#endif
+
 }  // namespace prefs
 }  // namespace password_manager
diff --git a/components/password_manager/core/common/password_manager_pref_names.h b/components/password_manager/core/common/password_manager_pref_names.h
index d328410a..3b9dbb7 100644
--- a/components/password_manager/core/common/password_manager_pref_names.h
+++ b/components/password_manager/core/common/password_manager_pref_names.h
@@ -228,15 +228,6 @@
 extern const char kProfileStoreDateLastUsedForFilling[];
 extern const char kAccountStoreDateLastUsedForFilling[];
 
-// A list of ongoing PasswordChangeSuccessTracker flows that is persisted in
-// case Chrome is temporarily shut down while, e.g., a user retrieves a
-// password reset email.
-extern const char kPasswordChangeSuccessTrackerFlows[];
-
-// Integer indicating the format version of the list saved under
-// |kPasswordChangeSuccessTrackerFlows|.
-extern const char kPasswordChangeSuccessTrackerVersion[];
-
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
 // Integer indicating how many times user saw biometric authentication before
 // filling promo.
@@ -272,6 +263,11 @@
 // sending and receiving passwords.
 extern const char kPasswordSharingEnabled[];
 
+#if BUILDFLAG(IS_MAC)
+// Integer pref indicating how many times relaunch Chrome bubble was dismissed.
+extern const char kRelaunchChromeBubbleDismissedCounter[];
+#endif
+
 }  // namespace password_manager::prefs
 
 #endif  // COMPONENTS_PASSWORD_MANAGER_CORE_COMMON_PASSWORD_MANAGER_PREF_NAMES_H_
diff --git a/components/password_manager_strings.grdp b/components/password_manager_strings.grdp
index b8ad4ed..4b998ae 100644
--- a/components/password_manager_strings.grdp
+++ b/components/password_manager_strings.grdp
@@ -203,6 +203,30 @@
     <message name="IDS_PASSWORD_MANAGER_BIOMETRIC_AUTHENTICATION_CONFIRMATION_DESCRIPTION_MAC" desc="Text that appears in the description of biometric authentication confirmation popup.">
       You can change whether you use your screen lock for filling passwords in <ph name="SETTINGS">$1<ex>Settings</ex></ph>
     </message>
+    <message name="IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_BRANDED" desc="Text that appears in the title of the relaunch Chrome bubble.">
+      Google Password Manager needs access to MacOS Keychain
+    </message>
+    <message name="IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_BRANDED" desc="Text that appears in the description of the relaunch Chrome bubble.">
+      To use Google Password Manager with macOS Keychain, relaunch Chrome and allow Keychain access. Your tabs will reopen after relaunching.
+    </message>
+    <message name="IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_BRANDED" desc="Text that appears in the accept button of the relaunch Chrome bubble.">
+      Relaunch Chrome
+    </message>
+    <message name="IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_NON_BRANDED" desc="Text that appears in the title of the relaunch Chromium bubble.">
+      Password Manager needs access to MacOS Keychain
+    </message>
+    <message name="IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_NON_BRANDED" desc="Text that appears in the description of the relaunch Chromium bubble.">
+      To use Password Manager with macOS Keychain, relaunch Chromium and allow Keychain access. Your tabs will reopen after relaunching.
+    </message>
+    <message name="IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_NON_BRANDED" desc="Text that appears in the accept button of the relaunch Chromium bubble.">
+      Relaunch Chromium
+    </message>
+    <message name="IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_CANCEL_BUTTON" desc="Text that appears in the cancel button of the relaunch Chrome bubble.">
+      Not Now
+    </message>
+    <message name="IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_NEVER_BUTTON" desc="Text that appears in the never button of the relaunch Chrome bubble.">
+      Don't Ask Again
+    </message>
   </if>
   <if expr="is_win or is_macosx">
     <message name="IDS_PASSWORD_MANAGER_BIOMETRIC_AUTHENTICATION_FOR_FILLING_PROMO_ACCEPT_BUTTON" desc="Text that appears in the Accept button of biometric authentication before filling popup.">
diff --git a/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_BRANDED.png.sha1 b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_BRANDED.png.sha1
new file mode 100644
index 0000000..637b069
--- /dev/null
+++ b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_BRANDED.png.sha1
@@ -0,0 +1 @@
+ab4d897db7af968c6de8074832b9ffd1b5b25dec
\ No newline at end of file
diff --git a/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_NON_BRANDED.png.sha1 b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_NON_BRANDED.png.sha1
new file mode 100644
index 0000000..d24a34697
--- /dev/null
+++ b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_ACCEPT_BUTTON_NON_BRANDED.png.sha1
@@ -0,0 +1 @@
+c16c5e96b4ffadc96fd389f17284c6886b087941
\ No newline at end of file
diff --git a/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_CANCEL_BUTTON.png.sha1 b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_CANCEL_BUTTON.png.sha1
new file mode 100644
index 0000000..d7901271
--- /dev/null
+++ b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_CANCEL_BUTTON.png.sha1
@@ -0,0 +1 @@
+37724cc5aa11ba033825b19777223a2b9e68703c
\ No newline at end of file
diff --git a/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_BRANDED.png.sha1 b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_BRANDED.png.sha1
new file mode 100644
index 0000000..637b069
--- /dev/null
+++ b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_BRANDED.png.sha1
@@ -0,0 +1 @@
+ab4d897db7af968c6de8074832b9ffd1b5b25dec
\ No newline at end of file
diff --git a/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_NON_BRANDED.png.sha1 b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_NON_BRANDED.png.sha1
new file mode 100644
index 0000000..d24a34697
--- /dev/null
+++ b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_DESCRIPTION_NON_BRANDED.png.sha1
@@ -0,0 +1 @@
+c16c5e96b4ffadc96fd389f17284c6886b087941
\ No newline at end of file
diff --git a/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_NEVER_BUTTON.png.sha1 b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_NEVER_BUTTON.png.sha1
new file mode 100644
index 0000000..7bdc9b5
--- /dev/null
+++ b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_NEVER_BUTTON.png.sha1
@@ -0,0 +1 @@
+65a63e592f6714d2118db980875abb8b48afd2f5
\ No newline at end of file
diff --git a/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_BRANDED.png.sha1 b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_BRANDED.png.sha1
new file mode 100644
index 0000000..637b069
--- /dev/null
+++ b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_BRANDED.png.sha1
@@ -0,0 +1 @@
+ab4d897db7af968c6de8074832b9ffd1b5b25dec
\ No newline at end of file
diff --git a/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_NON_BRANDED.png.sha1 b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_NON_BRANDED.png.sha1
new file mode 100644
index 0000000..d24a34697
--- /dev/null
+++ b/components/password_manager_strings_grdp/IDS_PASSWORD_MANAGER_RELAUNCH_CHROME_BUBBLE_TITLE_NON_BRANDED.png.sha1
@@ -0,0 +1 @@
+c16c5e96b4ffadc96fd389f17284c6886b087941
\ No newline at end of file
diff --git a/components/permissions/BUILD.gn b/components/permissions/BUILD.gn
index c489339..402c8d9 100644
--- a/components/permissions/BUILD.gn
+++ b/components/permissions/BUILD.gn
@@ -71,6 +71,8 @@
     "permission_context_base.h",
     "permission_decision_auto_blocker.cc",
     "permission_decision_auto_blocker.h",
+    "permission_hats_trigger_helper.cc",
+    "permission_hats_trigger_helper.h",
     "permission_manager.cc",
     "permission_manager.h",
     "permission_prompt.h",
@@ -198,8 +200,6 @@
       "bluetooth_scanning_prompt_controller.h",
       "bluetooth_scanning_prompt_desktop.cc",
       "bluetooth_scanning_prompt_desktop.h",
-      "permission_hats_trigger_helper.cc",
-      "permission_hats_trigger_helper.h",
       "permission_prompt_impl.cc",
     ]
   }
diff --git a/components/permissions/constants.cc b/components/permissions/constants.cc
index 343face..ef5cd98 100644
--- a/components/permissions/constants.cc
+++ b/components/permissions/constants.cc
@@ -13,7 +13,6 @@
 const char kEmbeddedContentHelpCenterURL[] =
     "https://support.google.com/chrome/?p=embedded_content";
 
-#if !BUILDFLAG(IS_ANDROID)
 // The key in `Product Specific String Data` under which the disposition of the
 // permission prompt is recorded in the prompt HaTS survey.
 const char kPermissionsPromptSurveyPromptDispositionKey[] = "PromptDisposition";
@@ -57,7 +56,6 @@
 // with min_version V with the rollout plan for stable. This filter allows
 // restriction to specific channels (typically to stable).
 const char kPermissionsPromptSurveyReleaseChannelKey[] = "ReleaseChannel";
-#endif
 
 // TODO(crbug.com/1410489): Remove the code related to unused site permissions
 // from Android builds.
diff --git a/components/permissions/constants.h b/components/permissions/constants.h
index 78f90a5..cc71378 100644
--- a/components/permissions/constants.h
+++ b/components/permissions/constants.h
@@ -21,7 +21,6 @@
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
 extern const char kEmbeddedContentHelpCenterURL[];
 
-#if !BUILDFLAG(IS_ANDROID)
 // The key in `Product Specific String Data` under which the disposition of the
 // permission prompt is recorded in the prompt HaTS survey.
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
@@ -71,7 +70,6 @@
 // restriction to specific channels (typically to stable).
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
 extern const char kPermissionsPromptSurveyReleaseChannelKey[];
-#endif
 
 // TODO(crbug.com/1410489): Remove the code related to unused site permissions
 // from Android builds.
diff --git a/components/permissions/features.cc b/components/permissions/features.cc
index 4e062232..841223f 100644
--- a/components/permissions/features.cc
+++ b/components/permissions/features.cc
@@ -80,16 +80,6 @@
              "PermissionPredictionsV2",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-#if BUILDFLAG(IS_ANDROID)
-
-// When enabled, blocks notifications permission prompt when Chrome doesn't
-// have app level Notification permission.
-BASE_FEATURE(kBlockNotificationPromptsIfDisabledOnAppLevel,
-             "BlockNotificationPromptsIfDisabledOnAppLevel",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-#else
-
 // Controls whether to trigger showing a HaTS survey, with the given
 // `probability` and `trigger_id`. The `probability` parameter is defined and
 // handled by the HatsService itself. If the parameter
@@ -106,6 +96,16 @@
              "PermissionsPromptSurvey",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+#if BUILDFLAG(IS_ANDROID)
+
+// When enabled, blocks notifications permission prompt when Chrome doesn't
+// have app level Notification permission.
+BASE_FEATURE(kBlockNotificationPromptsIfDisabledOnAppLevel,
+             "BlockNotificationPromptsIfDisabledOnAppLevel",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+#else
+
 // When enabled, permissions grants with a durable session model will have
 // an expiration date set. The interpretation of the expiration date
 // is not handled by this component, but left to the embedding browser.
@@ -181,7 +181,6 @@
     "holdback_chance",
     0.3);
 
-#if !BUILDFLAG(IS_ANDROID)
 // Specifies the `trigger_id` of the HaTS survey to trigger immediately after
 // the user has interacted with a permission prompt. Multiple values can be
 // configured by providing a comma separated list. If this is done, a
@@ -294,7 +293,6 @@
     kPermissionPromptSurveyOneTimePromptsDecidedBucket{
         &permissions::features::kPermissionsPromptSurvey,
         "one_time_prompts_decided_bucket", ""};
-#endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace feature_params
 }  // namespace permissions
diff --git a/components/permissions/features.h b/components/permissions/features.h
index 3e582527b..28ddc36 100644
--- a/components/permissions/features.h
+++ b/components/permissions/features.h
@@ -55,6 +55,9 @@
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
 BASE_DECLARE_FEATURE(kPermissionPredictionsV2);
 
+COMPONENT_EXPORT(PERMISSIONS_COMMON)
+BASE_DECLARE_FEATURE(kPermissionsPromptSurvey);
+
 #if BUILDFLAG(IS_ANDROID)
 
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
@@ -63,9 +66,6 @@
 #else
 
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
-BASE_DECLARE_FEATURE(kPermissionsPromptSurvey);
-
-COMPONENT_EXPORT(PERMISSIONS_COMMON)
 BASE_DECLARE_FEATURE(kRecordPermissionExpirationTimestamps);
 
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
@@ -109,7 +109,6 @@
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
 extern const base::FeatureParam<double> kPermissionPredictionsV2HoldbackChance;
 
-#if !BUILDFLAG(IS_ANDROID)
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
 extern const base::FeatureParam<std::string> kPermissionsPromptSurveyTriggerId;
 
@@ -151,7 +150,6 @@
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
 extern const base::FeatureParam<std::string>
     kPermissionPromptSurveyOneTimePromptsDecidedBucket;
-#endif
 
 }  // namespace feature_params
 }  // namespace permissions
diff --git a/components/permissions/permission_request_manager.cc b/components/permissions/permission_request_manager.cc
index 15cbbe3f..af4a87e 100644
--- a/components/permissions/permission_request_manager.cc
+++ b/components/permissions/permission_request_manager.cc
@@ -953,10 +953,9 @@
           "Notifications.Quiet.PermissionRequestShown"));
     }
 
-#if !BUILDFLAG(IS_ANDROID)
     PermissionsClient::Get()->TriggerPromptHatsSurveyIfEnabled(
-        web_contents()->GetBrowserContext(), requests_[0]->request_type(),
-        absl::nullopt, DetermineCurrentRequestUIDisposition(),
+        web_contents(), requests_[0]->request_type(), absl::nullopt,
+        DetermineCurrentRequestUIDisposition(),
         DetermineCurrentRequestUIDispositionReasonForUMA(),
         requests_[0]->GetGestureType(), absl::nullopt, false,
         web_contents()->GetLastCommittedURL(),
@@ -965,7 +964,6 @@
             : base::DoNothing());
 
     hats_shown_callback_.reset();
-#endif
   }
   current_request_already_displayed_ = true;
   current_request_first_display_time_ = base::Time::Now();
@@ -1031,17 +1029,23 @@
     time_to_decision_for_test_.reset();
   }
 
+  std::optional<permissions::PermissionIgnoredReason> ignore_reason =
+      absl::nullopt;
+#if !BUILDFLAG(IS_ANDROID)
+  // ignore reason metric currently not supported on android
+  if (permission_action == PermissionAction::IGNORED) {
+    ignore_reason = absl::make_optional(
+        PermissionsClient::Get()->DetermineIgnoreReason(web_contents()));
+  }
+#endif
+
   content::BrowserContext* browser_context =
       web_contents()->GetBrowserContext();
   PermissionUmaUtil::PermissionPromptResolved(
       requests_, web_contents(), permission_action, time_to_decision,
       DetermineCurrentRequestUIDisposition(),
       DetermineCurrentRequestUIDispositionReasonForUMA(),
-      prediction_grant_likelihood_, was_decision_held_back_,
-      permission_action == PermissionAction::IGNORED
-          ? absl::make_optional(
-                PermissionsClient::Get()->DetermineIgnoreReason(web_contents()))
-          : absl::nullopt,
+      prediction_grant_likelihood_, was_decision_held_back_, ignore_reason,
       did_show_prompt_, did_click_manage_, did_click_learn_more_);
 
   PermissionDecisionAutoBlocker* autoblocker =
diff --git a/components/permissions/permissions_client.cc b/components/permissions/permissions_client.cc
index 8003c094..c3d96d4 100644
--- a/components/permissions/permissions_client.cc
+++ b/components/permissions/permissions_client.cc
@@ -77,9 +77,8 @@
   return std::vector<std::unique_ptr<PermissionUiSelector>>();
 }
 
-#if !BUILDFLAG(IS_ANDROID)
 void PermissionsClient::TriggerPromptHatsSurveyIfEnabled(
-    content::BrowserContext* context,
+    content::WebContents* web_contents,
     permissions::RequestType request_type,
     absl::optional<permissions::PermissionAction> action,
     permissions::PermissionPromptDisposition prompt_disposition,
@@ -89,7 +88,6 @@
     bool is_post_prompt,
     const GURL& gurl,
     base::OnceCallback<void()> hats_shown_callback_) {}
-#endif
 
 void PermissionsClient::OnPromptResolved(
     RequestType request_type,
diff --git a/components/permissions/permissions_client.h b/components/permissions/permissions_client.h
index 92b7bf48..c8d6eb8e 100644
--- a/components/permissions/permissions_client.h
+++ b/components/permissions/permissions_client.h
@@ -161,9 +161,8 @@
 
   using QuietUiReason = PermissionUiSelector::QuietUiReason;
 
-#if !BUILDFLAG(IS_ANDROID)
   virtual void TriggerPromptHatsSurveyIfEnabled(
-      content::BrowserContext* context,
+      content::WebContents* web_contents,
       permissions::RequestType request_type,
       absl::optional<permissions::PermissionAction> action,
       permissions::PermissionPromptDisposition prompt_disposition,
@@ -173,7 +172,6 @@
       bool is_post_prompt,
       const GURL& gurl,
       base::OnceCallback<void()> hats_shown_callback_);
-#endif
 
   // Called for each request type when a permission prompt is resolved.
   virtual void OnPromptResolved(
diff --git a/components/permissions/pref_names.cc b/components/permissions/pref_names.cc
index f3d8ae3..97b77f1 100644
--- a/components/permissions/pref_names.cc
+++ b/components/permissions/pref_names.cc
@@ -34,11 +34,11 @@
 // the default search engine.
 const char kLocationSettingsNextShowDefault[] =
     "location_settings_next_show_default";
-#else   // BUILDFLAG(IS_ANDROID)
+#endif  // BUILDFLAG(IS_ANDROID)
+
 // The number of one time permission prompts a user has seen.
 const char kOneTimePermissionPromptsDecidedCount[] =
     "profile.one_time_permission_prompts_decided_count";
-#endif  // BUILDFLAG(IS_ANDROID)
 
 // Boolean that specifies whether or not unused site permissions should be
 // revoked by Safety Hub. It is used only when kSafetyHub flag is on.
diff --git a/components/permissions/pref_names.h b/components/permissions/pref_names.h
index ef91b6c..184e954 100644
--- a/components/permissions/pref_names.h
+++ b/components/permissions/pref_names.h
@@ -20,10 +20,10 @@
 extern const char kLocationSettingsBackoffLevelDefault[];
 extern const char kLocationSettingsNextShowDSE[];
 extern const char kLocationSettingsNextShowDefault[];
-#else
-extern const char kOneTimePermissionPromptsDecidedCount[];
 #endif
 
+extern const char kOneTimePermissionPromptsDecidedCount[];
+
 // The pref is used only when kSafetyHub flag is on.
 // Currently Safety Hub is available only on desktop.
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
diff --git a/components/reporting/proto/synced/metric_data.proto b/components/reporting/proto/synced/metric_data.proto
index 7554eeaa..422f9cb 100644
--- a/components/reporting/proto/synced/metric_data.proto
+++ b/components/reporting/proto/synced/metric_data.proto
@@ -407,12 +407,6 @@
   optional bool is_internal = 5;
 }
 
-// deliberately left empty as there is no payload for Heartbeat messages.
-// It just implicitly indicates that the sender is still alive.
-message KioskHeartbeatTelemetry {
-  // No payload
-}
-
 // Data that can change over time, collected and reported every specific period
 // of time or when an event occur.
 // IMPORTANT: Do not add additional non-message fields (bool, int, string,
@@ -444,8 +438,6 @@
   optional RuntimeCountersTelemetry runtime_counters_telemetry = 10;
   // Website telemetry data.
   optional WebsiteTelemetry website_telemetry = 11;
-  // Kiosk Heartbeats as telemetry data
-  optional KioskHeartbeatTelemetry heartbeat_telemetry = 12;
 }
 
 enum MetricEventType {
@@ -464,7 +456,6 @@
   CRASH_FATALLY = 17;
   URL_OPENED = 18;
   URL_CLOSED = 19;
-  KIOSK_HEARTBEAT = 20;
 
   reserved 2, 3, 10, 11, 12;
 }
diff --git a/components/signin/public/android/BUILD.gn b/components/signin/public/android/BUILD.gn
index a9e21b1..09f22f36 100644
--- a/components/signin/public/android/BUILD.gn
+++ b/components/signin/public/android/BUILD.gn
@@ -29,6 +29,7 @@
     "java/src/org/chromium/components/signin/AccountCapabilitiesFetcher.java",
     "java/src/org/chromium/components/signin/AccountEmailDomainDisplayability.java",
     "java/src/org/chromium/components/signin/AccountManagerDelegate.java",
+    "java/src/org/chromium/components/signin/AccountManagerDelegateException.java",
     "java/src/org/chromium/components/signin/AccountManagerFacade.java",
     "java/src/org/chromium/components/signin/AccountManagerFacadeImpl.java",
     "java/src/org/chromium/components/signin/AccountManagerFacadeProvider.java",
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/AccountManagerDelegate.java b/components/signin/public/android/java/src/org/chromium/components/signin/AccountManagerDelegate.java
index f625ad1..8b561a73 100644
--- a/components/signin/public/android/java/src/org/chromium/components/signin/AccountManagerDelegate.java
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/AccountManagerDelegate.java
@@ -48,12 +48,15 @@
     @MainThread
     void attachAccountsChangeObserver(AccountsChangeObserver observer);
 
-    /**
-     * Get all the accounts on device synchronously.
-     */
+    /** Get all the accounts on device synchronously. */
+    @Deprecated
     @WorkerThread
     Account[] getAccounts();
 
+    /** Get all the accounts on device synchronously. */
+    @WorkerThread
+    Account[] getAccountsSynchronous() throws AccountManagerDelegateException;
+
     /**
      * Get an auth token.
      *
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/AccountManagerDelegateException.java b/components/signin/public/android/java/src/org/chromium/components/signin/AccountManagerDelegateException.java
new file mode 100644
index 0000000..dbbbde27
--- /dev/null
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/AccountManagerDelegateException.java
@@ -0,0 +1,15 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.signin;
+
+/**
+ * AccountManagerDelegateException encapsulates errors that can happen while getting list of
+ * accounts.
+ */
+public class AccountManagerDelegateException extends Exception {
+    public AccountManagerDelegateException(String message) {
+        super(message);
+    }
+}
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java b/components/signin/public/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java
index 318b535f..351a526 100644
--- a/components/signin/public/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java
@@ -85,6 +85,7 @@
                 context, receiver, gmsPackageReplacedFilter);
     }
 
+    @Deprecated
     @Override
     public Account[] getAccounts() {
         if (hasGetAccountsPermission() && isGooglePlayServicesAvailable()) {
@@ -101,6 +102,25 @@
     }
 
     @Override
+    public Account[] getAccountsSynchronous() throws AccountManagerDelegateException {
+        if (!isGooglePlayServicesAvailable()) {
+            throw new AccountManagerDelegateException("Can't use Google Play Services");
+        }
+        if (hasGetAccountsPermission()) {
+            long startTime = SystemClock.elapsedRealtime();
+            Account[] accounts =
+                    mAccountManager.getAccountsByType(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
+            RecordHistogram.recordTimesHistogram(
+                    "Signin.AndroidGetAccountsTime_AccountManager",
+                    SystemClock.elapsedRealtime() - startTime);
+            return accounts;
+        }
+        // Don't report any accounts if we don't have permission.
+        // TODO(crbug.com/1502123): Throw an exception if permission was denied.
+        return new Account[] {};
+    }
+
+    @Override
     public AccessTokenData getAuthToken(Account account, String authTokenScope)
             throws AuthException {
         ThreadUtils.assertOnBackgroundThread();
diff --git a/components/signin/public/android/java/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java b/components/signin/public/android/java/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
index 527620b..7ab9c67d 100644
--- a/components/signin/public/android/java/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
+++ b/components/signin/public/android/java/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java
@@ -17,6 +17,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.components.signin.AccessTokenData;
 import org.chromium.components.signin.AccountManagerDelegate;
+import org.chromium.components.signin.AccountManagerDelegateException;
 import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.AccountsChangeObserver;
 import org.chromium.components.signin.AuthException;
@@ -29,11 +30,11 @@
 /**
  * The FakeAccountManagerDelegate is intended for testing components that use AccountManagerFacade.
  *
- * You should provide a set of accounts as a constructor argument, or use the more direct approach
- * and provide an array of AccountHolder objects.
+ * <p>You should provide a set of accounts as a constructor argument, or use the more direct
+ * approach and provide an array of AccountHolder objects.
  *
- * Currently, this implementation supports adding and removing accounts, handling credentials
- * (including confirming them), and handling of dummy auth tokens.
+ * <p>Currently, this implementation supports adding and removing accounts, handling credentials
+ * (including confirming them), and handling of placeholder auth tokens.
  */
 public class FakeAccountManagerDelegate implements AccountManagerDelegate {
     private final Object mLock = new Object();
@@ -57,6 +58,7 @@
         mObserver = observer;
     }
 
+    @Deprecated
     @Override
     public Account[] getAccounts() {
         ArrayList<Account> result = new ArrayList<>();
@@ -68,6 +70,17 @@
         return result.toArray(new Account[0]);
     }
 
+    @Override
+    public Account[] getAccountsSynchronous() throws AccountManagerDelegateException {
+        ArrayList<Account> result = new ArrayList<>();
+        synchronized (mLock) {
+            for (AccountHolder ah : mAccounts) {
+                result.add(ah.getAccount());
+            }
+        }
+        return result.toArray(new Account[0]);
+    }
+
     /**
      * Adds an AccountHolder.
      */
diff --git a/components/signin/public/android/junit/src/org/chromium/components/signin/SystemAccountManagerDelegateTest.java b/components/signin/public/android/junit/src/org/chromium/components/signin/SystemAccountManagerDelegateTest.java
index ead3f6c..6b6e9ac 100644
--- a/components/signin/public/android/junit/src/org/chromium/components/signin/SystemAccountManagerDelegateTest.java
+++ b/components/signin/public/android/junit/src/org/chromium/components/signin/SystemAccountManagerDelegateTest.java
@@ -6,21 +6,30 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AccountManagerCallback;
 import android.accounts.AccountManagerFuture;
 import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 
+import com.google.android.gms.auth.GoogleAuthUtil;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,7 +38,9 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.components.externalauth.ExternalAuthUtils;
 
 import java.io.IOException;
 import java.util.concurrent.atomic.AtomicReference;
@@ -38,10 +49,16 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class SystemAccountManagerDelegateTest {
+    private static final Account[] GOOGLE_ACCOUNTS = {
+        AccountUtils.createAccountFromName("account-name"),
+        AccountUtils.createAccountFromName("other-account-name")
+    };
     @Mock private AccountManager mAccountManager;
     @Mock private AccountManagerFuture<Bundle> mAccountManagerFuture;
     @Mock private Account mAccount;
     @Mock private Activity mActivity;
+    @Mock private ExternalAuthUtils mExternalAuthUtils;
+    @Mock private Context mContext;
 
     private final AtomicReference<Bundle> mConfirmCredentialsResponse = new AtomicReference<>();
     private SystemAccountManagerDelegate mDelegate;
@@ -51,6 +68,15 @@
         MockitoAnnotations.initMocks(this);
 
         mAccountManager = Mockito.mock(AccountManager.class);
+        doReturn(GOOGLE_ACCOUNTS)
+                .when(mAccountManager)
+                .getAccountsByType(eq(GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE));
+
+        when(mExternalAuthUtils.canUseGooglePlayServices()).thenReturn(true);
+        ExternalAuthUtils.setInstanceForTesting(mExternalAuthUtils);
+        when(mContext.checkPermission(eq(Manifest.permission.GET_ACCOUNTS), anyInt(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        ContextUtils.initApplicationContextForTests(mContext);
         mDelegate = new SystemAccountManagerDelegate(mAccountManager);
     }
 
@@ -94,4 +120,30 @@
                 .confirmCredentials(
                         eq(mAccount), any(Bundle.class), eq(mActivity), any(), eq(null));
     }
+
+    @Test
+    public void testGetAccountsSynchronous_returnsAccounts() throws Exception {
+        Account[] accounts = mDelegate.getAccountsSynchronous();
+
+        assertSame(GOOGLE_ACCOUNTS, accounts);
+    }
+
+    @Test
+    public void testGetAccountsSynchronous_doesNotHavePermission_returnsEmptyList()
+            throws Exception {
+        when(mContext.checkPermission(eq(Manifest.permission.GET_ACCOUNTS), anyInt(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        Account[] accounts = mDelegate.getAccountsSynchronous();
+
+        assertEquals(0, accounts.length);
+    }
+
+    @Test
+    public void testGetAccountsSynchronous_throwsException() {
+        when(mExternalAuthUtils.canUseGooglePlayServices()).thenReturn(false);
+
+        assertThrows(
+                AccountManagerDelegateException.class, () -> mDelegate.getAccountsSynchronous());
+    }
 }
diff --git a/content/browser/renderer_host/render_widget_host_view_ios.h b/content/browser/renderer_host/render_widget_host_view_ios.h
index a4e25879..76a8ff7 100644
--- a/content/browser/renderer_host/render_widget_host_view_ios.h
+++ b/content/browser/renderer_host/render_widget_host_view_ios.h
@@ -86,6 +86,7 @@
   void ClearFallbackSurfaceForCommitPending() override;
   void ResetFallbackToFirstNavigationSurface() override;
   viz::FrameSinkId GetRootFrameSinkId() override;
+  void UpdateFrameSinkIdRegistration() override;
   const viz::FrameSinkId& GetFrameSinkId() const override;
   const viz::LocalSurfaceId& GetLocalSurfaceId() const override;
   viz::SurfaceId GetCurrentSurfaceId() const override;
diff --git a/content/browser/renderer_host/render_widget_host_view_ios.mm b/content/browser/renderer_host/render_widget_host_view_ios.mm
index 89a54c4..7088f12 100644
--- a/content/browser/renderer_host/render_widget_host_view_ios.mm
+++ b/content/browser/renderer_host/render_widget_host_view_ios.mm
@@ -475,6 +475,12 @@
   return browser_compositor_->GetRendererLocalSurfaceId();
 }
 
+void RenderWidgetHostViewIOS::UpdateFrameSinkIdRegistration() {
+  RenderWidgetHostViewBase::UpdateFrameSinkIdRegistration();
+  browser_compositor_->GetDelegatedFrameHost()->SetIsFrameSinkIdOwner(
+      is_frame_sink_id_owner());
+}
+
 const viz::FrameSinkId& RenderWidgetHostViewIOS::GetFrameSinkId() const {
   return browser_compositor_->GetDelegatedFrameHost()->frame_sink_id();
 }
diff --git a/content/browser/service_worker/service_worker_metrics.cc b/content/browser/service_worker/service_worker_metrics.cc
index d6bc133..2e8212b4 100644
--- a/content/browser/service_worker/service_worker_metrics.cc
+++ b/content/browser/service_worker/service_worker_metrics.cc
@@ -337,11 +337,15 @@
             "ServiceWorker.InstallEvent.WithFetch.Time", time);
       }
       break;
+    case EventType::MESSAGE:
+      UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.ExtendableMessageEvent.Time",
+                                 time);
+      break;
     case EventType::FETCH_MAIN_FRAME:
     case EventType::FETCH_SUB_FRAME:
-    case EventType::FETCH_FENCED_FRAME:
     case EventType::FETCH_SHARED_WORKER:
     case EventType::FETCH_SUB_RESOURCE:
+    case EventType::FETCH_FENCED_FRAME:
       if (was_handled) {
         UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.FetchEvent.HasResponse.Time",
                                    time);
@@ -350,49 +354,10 @@
                                    time);
       }
       break;
-    case EventType::FETCH_WAITUNTIL:
-      // Do nothing: the histogram has been removed.
-      break;
-    case EventType::SYNC:
-      UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.BackgroundSyncEvent.Time",
-                                 time);
-      break;
-    case EventType::NOTIFICATION_CLICK:
-      // Do nothing: the histogram has been removed.
-      break;
-    case EventType::NOTIFICATION_CLOSE:
-      // Do nothing: the histogram has been removed.
-      break;
-    case EventType::PUSH:
-      UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.PushEvent.Time", time);
-      break;
-    case EventType::MESSAGE:
-      UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.ExtendableMessageEvent.Time",
-                                 time);
-      break;
-    case EventType::EXTERNAL_REQUEST:
-      // Do nothing: the histogram has been removed.
-      break;
     case EventType::PAYMENT_REQUEST:
       UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.PaymentRequestEvent.Time",
                                  time);
       break;
-    case EventType::BACKGROUND_FETCH_ABORT:
-      UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.BackgroundFetchAbortEvent.Time",
-                                 time);
-      break;
-    case EventType::BACKGROUND_FETCH_CLICK:
-      UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.BackgroundFetchClickEvent.Time",
-                                 time);
-      break;
-    case EventType::BACKGROUND_FETCH_FAIL:
-      UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.BackgroundFetchFailEvent.Time",
-                                 time);
-      break;
-    case EventType::BACKGROUND_FETCH_SUCCESS:
-      UMA_HISTOGRAM_MEDIUM_TIMES(
-          "ServiceWorker.BackgroundFetchSuccessEvent.Time", time);
-      break;
     case EventType::CAN_MAKE_PAYMENT:
       UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.CanMakePaymentEvent.Time",
                                  time);
@@ -400,26 +365,30 @@
     case EventType::ABORT_PAYMENT:
       UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.AbortPaymentEvent.Time", time);
       break;
-    case EventType::COOKIE_CHANGE:
-      // Do nothing: the histogram has been removed.
-      break;
     case EventType::PERIODIC_SYNC:
       UMA_HISTOGRAM_MEDIUM_TIMES(
           "ServiceWorker.PeriodicBackgroundSyncEvent.Time", time);
       break;
+    case EventType::SYNC:
+    case EventType::NOTIFICATION_CLICK:
+    case EventType::PUSH:
+    case EventType::NOTIFICATION_CLOSE:
+    case EventType::FETCH_WAITUNTIL:
+    case EventType::EXTERNAL_REQUEST:
+    case EventType::BACKGROUND_FETCH_ABORT:
+    case EventType::BACKGROUND_FETCH_CLICK:
+    case EventType::BACKGROUND_FETCH_FAIL:
+    case EventType::COOKIE_CHANGE:
+    case EventType::BACKGROUND_FETCH_SUCCESS:
     case EventType::CONTENT_DELETE:
-      // Do nothing: the histogram has been removed.
-      break;
     case EventType::PUSH_SUBSCRIPTION_CHANGE:
-      // Do nothing: the histogram has been removed.
-      break;
     case EventType::WARM_UP:
       // Do nothing: the warm up should not be sent as an event.
       break;
-    case EventType::BYPASS_MAIN_RESOURCE:
-    // The bypass main resource should not be sent as an event.
     case EventType::NAVIGATION_HINT:
     // The navigation hint should not be sent as an event.
+    case EventType::BYPASS_MAIN_RESOURCE:
+    // The bypass main resource should not be sent as an event.
     case EventType::SKIP_EMPTY_FETCH_HANDLER:
     // The skip empty fetch handler should not be sent as an event.
     case EventType::BYPASS_ONLY_IF_SERVICE_WORKER_NOT_STARTED:
diff --git a/content/common/service_worker/service_worker_router_evaluator.cc b/content/common/service_worker/service_worker_router_evaluator.cc
index cdf65c1..f4721ae 100644
--- a/content/common/service_worker/service_worker_router_evaluator.cc
+++ b/content/common/service_worker/service_worker_router_evaluator.cc
@@ -40,7 +40,8 @@
   kInvalidSource = 6,
   kInvalidCondition = 7,
   kExceedMaxConditionDepth = 8,
-  kMaxValue = kExceedMaxConditionDepth,
+  kExceedMaxRouterSize = 9,
+  kMaxValue = kExceedMaxRouterSize,
 };
 
 void RecordSetupError(ServiceWorkerRouterEvaluatorErrorEnums e) {
@@ -536,8 +537,7 @@
 
 class ServiceWorkerRouterEvaluator::RouterRule {
  public:
-  bool SetRule(const blink::ServiceWorkerRouterRule& rule,
-               const std::string& id) {
+  bool SetRule(const blink::ServiceWorkerRouterRule& rule, std::uint32_t id) {
     if (ExceedsMaxConditionDepth(rule.condition)) {
       // Too many recursion in the condition.
       RecordSetupError(
@@ -557,7 +557,7 @@
   // Rule ID is allocated when the router is set. ID is unique within one
   // service worker registration, but may overlap among other routers of other
   // registrations.
-  const std::string& id() const { return id_; }
+  std::uint32_t id() const { return id_; }
   bool need_running_status() const { return condition_.need_running_status(); }
 
  private:
@@ -573,7 +573,7 @@
 
   ConditionObject condition_;
   std::vector<blink::ServiceWorkerRouterSource> sources_;
-  std::string id_;
+  std::uint32_t id_;
 };
 
 ServiceWorkerRouterEvaluator::Result::Result() = default;
@@ -588,11 +588,16 @@
 ServiceWorkerRouterEvaluator::~ServiceWorkerRouterEvaluator() = default;
 
 void ServiceWorkerRouterEvaluator::Compile() {
+  if (rules_.rules.size() >= blink::kServiceWorkerMaxRouterSize) {
+    RecordSetupError(
+        ServiceWorkerRouterEvaluatorErrorEnums::kExceedMaxRouterSize);
+    return;
+  }
   for (size_t idx = 0; idx < rules_.rules.size(); ++idx) {
     const auto& r = rules_.rules[idx];
     std::unique_ptr<RouterRule> rule = std::make_unique<RouterRule>();
     // For now, use index as rule ID (1-indexed)
-    const std::string id = base::NumberToString(idx + 1);
+    std::uint32_t id = idx + 1;
     if (!rule->SetRule(r, id)) {
       return;
     }
diff --git a/content/common/service_worker/service_worker_router_evaluator.h b/content/common/service_worker/service_worker_router_evaluator.h
index c69f60f..ef89e2ac 100644
--- a/content/common/service_worker/service_worker_router_evaluator.h
+++ b/content/common/service_worker/service_worker_router_evaluator.h
@@ -31,7 +31,7 @@
     Result(const Result& other) = delete;
     Result& operator=(const Result&) = delete;
 
-    std::string id;
+    std::uint32_t id = 0;
     std::vector<blink::ServiceWorkerRouterSource> sources;
   };
 
diff --git a/content/public/common/content_client.h b/content/public/common/content_client.h
index 26bf98b..5d14846 100644
--- a/content/public/common/content_client.h
+++ b/content/public/common/content_client.h
@@ -223,7 +223,7 @@
   // The embedder API for participating in gpu logic.
   raw_ptr<ContentGpuClient> gpu_;
   // The embedder API for participating in renderer logic.
-  raw_ptr<ContentRendererClient, LeakedDanglingUntriaged> renderer_;
+  raw_ptr<ContentRendererClient> renderer_;
   // The embedder API for participating in utility logic.
   raw_ptr<ContentUtilityClient> utility_;
 };
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc
index fee37e6..94977e8 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl_unittest.cc
@@ -8,7 +8,6 @@
 #include <cstddef>
 #include <memory>
 
-#include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/test/gtest_util.h"
@@ -383,7 +382,7 @@
     ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_context_provider_));
     ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_context_gl_));
     ASSERT_TRUE(testing::Mock::VerifyAndClear(&mock_gpu_channel_));
-    delete gpu_command_buffer_proxy_;
+    gpu_command_buffer_proxy_.reset();
     mock_context_provider_.reset();
     gpu_channel_host_.reset();
   }
@@ -419,7 +418,7 @@
     ON_CALL(*mock_context_provider_, ContextGL())
         .WillByDefault(Return(&mock_context_gl_));
 
-    gpu_command_buffer_proxy_ = new gpu::CommandBufferProxyImpl(
+    gpu_command_buffer_proxy_ = std::make_unique<gpu::CommandBufferProxyImpl>(
         gpu_channel_host_, content::kGpuStreamIdDefault,
         task_environment_.GetMainThreadTaskRunner());
     gpu_command_buffer_proxy_->Initialize(
@@ -494,8 +493,7 @@
   viz::TestGpuMemoryBufferManager gpu_memory_buffer_manager_;
   scoped_refptr<TestGpuChannelHost> gpu_channel_host_;
   scoped_refptr<MockContextProviderCommandBuffer> mock_context_provider_;
-  raw_ptr<gpu::CommandBufferProxyImpl, DanglingUntriaged>
-      gpu_command_buffer_proxy_;
+  std::unique_ptr<gpu::CommandBufferProxyImpl> gpu_command_buffer_proxy_;
 
   FakeVEAProviderImpl fake_vea_provider_;
 
diff --git a/content/renderer/render_thread_impl_browsertest.cc b/content/renderer/render_thread_impl_browsertest.cc
index 3a3b5f60..f2d82ddf 100644
--- a/content/renderer/render_thread_impl_browsertest.cc
+++ b/content/renderer/render_thread_impl_browsertest.cc
@@ -216,6 +216,7 @@
   }
 
   void TearDown() override {
+    SetRendererClientForTesting(nullptr);
     CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
         switches::kSingleProcessTests));
     // In a single-process mode, we need to avoid destructing `process_`
diff --git a/gpu/ipc/in_process_command_buffer.cc b/gpu/ipc/in_process_command_buffer.cc
index 0c7d56b..5a46d8b0 100644
--- a/gpu/ipc/in_process_command_buffer.cc
+++ b/gpu/ipc/in_process_command_buffer.cc
@@ -276,14 +276,15 @@
 
   context_state_ = task_executor_->GetSharedContextState();
 
+  scoped_refptr<gl::GLSurface> surface;
   if (context_state_) {
-    surface_ = context_state_->surface();
+    surface = context_state_->surface();
   } else {
     // TODO(crbug.com/1247756): Is creating an offscreen GL surface needed
     // still?
-    surface_ = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(),
-                                                  gfx::Size());
-    if (!surface_.get()) {
+    surface = gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(),
+                                                 gfx::Size());
+    if (!surface) {
       DestroyOnGpuThread();
       LOG(ERROR) << "ContextResult::kFatalFailure: Failed to create surface.";
       return gpu::ContextResult::kFatalFailure;
@@ -358,13 +359,13 @@
           use_virtualized_gl_context_ ? gl_share_group_->shared_context()
                                       : nullptr;
       if (real_context &&
-          (!real_context->MakeCurrent(surface_.get()) ||
+          (!real_context->MakeCurrent(surface.get()) ||
            real_context->CheckStickyGraphicsResetStatus() != GL_NO_ERROR)) {
         real_context = nullptr;
       }
       if (!real_context) {
         real_context = gl::init::CreateGLContext(
-            gl_share_group_.get(), surface_.get(),
+            gl_share_group_.get(), surface.get(),
             GenerateGLContextAttribsForDecoder(*params.attribs,
                                                context_group_.get()));
         if (!real_context) {
@@ -385,7 +386,7 @@
           gl_share_group_->SetSharedContext(real_context.get());
       }
 
-      if (!real_context->MakeCurrent(surface_.get())) {
+      if (!real_context->MakeCurrent(surface.get())) {
         LOG(ERROR) << "ContextResult::kTransientFailure, failed to make "
                       "context current";
         DestroyOnGpuThread();
@@ -398,7 +399,7 @@
       if (use_virtualized_gl_context_) {
         context_ = base::MakeRefCounted<GLContextVirtual>(
             gl_share_group_.get(), real_context.get(), decoder_->AsWeakPtr());
-        if (!context_->Initialize(surface_.get(),
+        if (!context_->Initialize(surface.get(),
                                   GenerateGLContextAttribsForDecoder(
                                       *params.attribs, context_group_.get()))) {
           // TODO(piman): This might not be fatal, we could recurse into
@@ -410,7 +411,7 @@
           return gpu::ContextResult::kFatalFailure;
         }
 
-        if (!context_->MakeCurrent(surface_.get())) {
+        if (!context_->MakeCurrent(surface.get())) {
           DestroyOnGpuThread();
           // The caller should retry making a context, but this one won't work.
           LOG(ERROR) << "ContextResult::kTransientFailure: "
@@ -419,7 +420,7 @@
         }
       } else {
         context_ = real_context;
-        DCHECK(context_->IsCurrent(surface_.get()));
+        DCHECK(context_->IsCurrent(surface.get()));
       }
     }
 
@@ -427,10 +428,11 @@
         !context_group_->feature_info()->workarounds().disable_program_cache) {
       context_group_->set_program_cache(task_executor_->program_cache());
     }
+    DCHECK(context_->default_surface());
   }
 
   gles2::DisallowedFeatures disallowed_features;
-  auto result = decoder_->Initialize(surface_, context_, /*offscreen=*/true,
+  auto result = decoder_->Initialize(surface, context_, /*offscreen=*/true,
                                      disallowed_features, *params.attribs);
   if (result != gpu::ContextResult::kSuccess) {
     DestroyOnGpuThread();
@@ -448,7 +450,7 @@
     // the case that this command decoder is the next one to be
     // processed, force a "full virtual" MakeCurrent to be performed.
     context_->ForceReleaseVirtuallyCurrent();
-    if (!context_->MakeCurrent(surface_.get())) {
+    if (!context_->MakeCurrent(surface.get())) {
       DestroyOnGpuThread();
       LOG(ERROR) << "ContextResult::kTransientFailure: "
                     "Failed to make context current after initialization.";
@@ -491,7 +493,7 @@
 
   gpu_thread_weak_ptr_factory_.InvalidateWeakPtrs();
   // Clean up GL resources if possible.
-  bool have_context = context_.get() && context_->MakeCurrent(surface_.get());
+  bool have_context = context_.get() && context_->MakeCurrentDefault();
   std::optional<gles2::ProgramCache::ScopedCacheUse> cache_use;
   if (have_context)
     CreateCacheUse(cache_use);
@@ -501,7 +503,6 @@
     decoder_.reset();
   }
   command_buffer_.reset();
-  surface_ = nullptr;
 
   context_ = nullptr;
   if (sync_point_client_state_) {
diff --git a/gpu/ipc/in_process_command_buffer.h b/gpu/ipc/in_process_command_buffer.h
index 9319dbb..35c2eeed 100644
--- a/gpu/ipc/in_process_command_buffer.h
+++ b/gpu/ipc/in_process_command_buffer.h
@@ -274,7 +274,6 @@
   std::unique_ptr<CommandBufferService> command_buffer_;
   std::unique_ptr<DecoderContext> decoder_;
   scoped_refptr<gl::GLContext> context_;
-  scoped_refptr<gl::GLSurface> surface_;
   scoped_refptr<SyncPointClientState> sync_point_client_state_;
 
   // Used to throttle PerformDelayedWorkOnGpuThread.
diff --git a/ios/chrome/browser/shared/ui/elements/top_aligned_image_view.h b/ios/chrome/browser/shared/ui/elements/top_aligned_image_view.h
index 3037d4d..5fb9ba2 100644
--- a/ios/chrome/browser/shared/ui/elements/top_aligned_image_view.h
+++ b/ios/chrome/browser/shared/ui/elements/top_aligned_image_view.h
@@ -8,17 +8,17 @@
 #import <UIKit/UIKit.h>
 
 // The standard UIImageView zooms to the center of the image when it aspect
-// fills. TopAlignedImageView aligns to the top of the image in all cases.
-// When the image is portrait, the image matches the width of the view in all
-// cases. When the image is landscape, the image matches the height of the view
-// in all cases.
+// fills. TopAlignedImageView aligns to the top and width of the image in all
+// cases.
 @interface TopAlignedImageView : UIView
+
 // The image displayed in the image view.
 @property(nonatomic) UIImage* image;
 
 - (instancetype)init NS_DESIGNATED_INITIALIZER;
 - (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
 - (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_SHARED_UI_ELEMENTS_TOP_ALIGNED_IMAGE_VIEW_H_
diff --git a/ios/chrome/browser/shared/ui/elements/top_aligned_image_view.mm b/ios/chrome/browser/shared/ui/elements/top_aligned_image_view.mm
index 7994e3d..5713c84 100644
--- a/ios/chrome/browser/shared/ui/elements/top_aligned_image_view.mm
+++ b/ios/chrome/browser/shared/ui/elements/top_aligned_image_view.mm
@@ -30,20 +30,10 @@
   if (imageSize.width == 0 || imageSize.height == 0) {
     return;
   }
-  CGFloat widthScaleFactor = CGRectGetWidth(self.frame) / imageSize.width;
-  CGFloat heightScaleFactor = CGRectGetHeight(self.frame) / imageSize.height;
-  CGFloat imageViewWidth;
-  CGFloat imageViewHeight;
-  if (imageSize.width > imageSize.height) {
-    imageViewWidth = imageSize.width * heightScaleFactor;
-    imageViewHeight = CGRectGetHeight(self.frame);
-  } else {
-    imageViewWidth = CGRectGetWidth(self.frame);
-    imageViewHeight = imageSize.height * widthScaleFactor;
-  }
-  self.innerImageView.frame =
-      CGRectMake((self.frame.size.width - imageViewWidth) / 2.0f, 0,
-                 imageViewWidth, imageViewHeight);
+  CGFloat widthScaleFactor = CGRectGetWidth(self.bounds) / imageSize.width;
+  CGFloat imageViewWidth = CGRectGetWidth(self.bounds);
+  CGFloat imageViewHeight = imageSize.height * widthScaleFactor;
+  self.innerImageView.frame = CGRectMake(0, 0, imageViewWidth, imageViewHeight);
 }
 
 #pragma mark - Public properties
diff --git a/ios/chrome/common/ui/util/button_util.mm b/ios/chrome/common/ui/util/button_util.mm
index 50483f8..c1f0c06 100644
--- a/ios/chrome/common/ui/util/button_util.mm
+++ b/ios/chrome/common/ui/util/button_util.mm
@@ -13,6 +13,7 @@
 
 UIButton* PrimaryActionButton(BOOL pointer_interaction_enabled) {
   UIButton* primary_blue_button = [UIButton buttonWithType:UIButtonTypeSystem];
+  primary_blue_button.translatesAutoresizingMaskIntoConstraints = NO;
 
   if (@available(iOS 15.0, *)) {
     UIButtonConfiguration* buttonConfiguration =
diff --git a/ios/web/annotations/resources/annotations.ts b/ios/web/annotations/resources/annotations.ts
index 317f5fe..a1e54989 100644
--- a/ios/web/annotations/resources/annotations.ts
+++ b/ios/web/annotations/resources/annotations.ts
@@ -71,12 +71,10 @@
   constructor(private readonly initialEvent: Event) {
     this.mutationObserver =
         new MutationObserver((mutationList: MutationRecord[]) => {
-          if (this.hasMutations) {
-            return;
-          }
           for (let mutation of mutationList) {
             if (mutation.target.contains(this.initialEvent.target as Node)) {
               this.hasMutations = true;
+              this.stopObserving();
               break;
             }
           }
@@ -218,8 +216,9 @@
   let failures = 0;
   decorations = [];
 
-  // Last checks when bubbling up event.
+  // Check CHROME_ANNOTATION on capturing and bubbling event.
   document.addEventListener('click', handleTopTap.bind(document));
+  document.addEventListener('click', handleTopTap.bind(document), true);
 
   annotations = removeOverlappingAnnotations(annotations);
 
@@ -527,14 +526,6 @@
 
 let mutationDuringClickObserver: MutationsDuringClickTracker|null;
 
-// Initiates a `mutationDuringClickObserver` that will be checked at document
-// level tab handler (`handleTopTap`), where it will be decided if any action
-// bubbling to objc is required (i.e. no DOM change occurs).
-function handleTap(event: Event) {
-  cancelObserver();
-  mutationDuringClickObserver = new MutationsDuringClickTracker(event);
-}
-
 // Stops observing DOM mutations.
 function cancelObserver(): void {
   mutationDuringClickObserver?.stopObserving();
@@ -544,24 +535,36 @@
 // Monitors taps at the top, document level. This checks if it is tap
 // triggered by an annotation and if no DOM mutation have happened while the
 // event is bubbling up. If it's the case, the annotation callback is called.
-function handleTopTap(event: Event) {
-  // Nothing happened to the page between `handleTap` and `handleTopTap`.
-  if (event.target instanceof HTMLElement &&
-      event.target.tagName === 'CHROME_ANNOTATION' &&
-      mutationDuringClickObserver &&
-      !mutationDuringClickObserver.hasPreventativeActivity(event)) {
-    const annotation = event.target;
-    mutationDuringClickObserver.extendObservation(() => {
-      if (mutationDuringClickObserver) {
-        highlightAnnotation(annotation);
+function handleTopTap(event: Event): void {
+  const annotation = event.target;
+  if (annotation instanceof HTMLElement &&
+      annotation.tagName === 'CHROME_ANNOTATION') {
+    if (event.eventPhase === Event.CAPTURING_PHASE) {
+      // Initiates a `mutationDuringClickObserver` that will be checked at
+      // bubble up phase where it will be decided if the click should be
+      // cancelled.
+      cancelObserver();
+      mutationDuringClickObserver = new MutationsDuringClickTracker(event);
+    } else if (mutationDuringClickObserver) {
+      // At BUBBLING_PHASE.
+      if (!mutationDuringClickObserver.hasPreventativeActivity(event)) {
+        mutationDuringClickObserver.extendObservation(() => {
+          if (mutationDuringClickObserver) {
+            highlightAnnotation(annotation);
+            onClickAnnotation(
+                annotation, mutationDuringClickObserver.hasMutations);
+          }
+        });
+      } else {
         onClickAnnotation(annotation, mutationDuringClickObserver.hasMutations);
       }
-    });
+    }
   } else {
-    onClickAnnotation(event.target as HTMLElement, true);
+    cancelObserver();
   }
 }
 
+// Sends click to Bling and cancel observer.
 function onClickAnnotation(annotation: HTMLElement, cancel: boolean): void {
   sendWebKitMessage('annotations', {
     command: 'annotations.onClick',
@@ -658,7 +661,6 @@
     }
 
     element.style.borderBottomColor = textColor;
-    element.addEventListener('click', handleTap.bind(element), true);
     parts.push(element);
     cursor = replacement.right;
   }
diff --git a/ios_internal b/ios_internal
index db86abed..37da2fd 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit db86abedd528e43509aea0f3aa9e7720a3a1ca91
+Subproject commit 37da2fd52608c0c45a507747657e6161829181e3
diff --git a/media/capture/video/win/video_capture_device_mf_win.cc b/media/capture/video/win/video_capture_device_mf_win.cc
index 94afdb8..e42105f 100644
--- a/media/capture/video/win/video_capture_device_mf_win.cc
+++ b/media/capture/video/win/video_capture_device_mf_win.cc
@@ -1772,6 +1772,24 @@
               ? mojom::BackgroundBlurMode::BLUR
               : mojom::BackgroundBlurMode::OFF;
     }
+
+    hr = extended_camera_controller_->GetExtendedCameraControl(
+        MF_CAPTURE_ENGINE_MEDIASOURCE,
+        KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW,
+        &extended_camera_control);
+    DLOG_IF_FAILED_WITH_HRESULT(
+        "Failed to retrieve IMFExtendedCameraControl for digital window", hr);
+    if (SUCCEEDED(hr) &&
+        (extended_camera_control->GetCapabilities() &
+         KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_AUTOFACEFRAMING)) {
+      photo_capabilities->supported_face_framing_modes = {
+          mojom::MeteringMode::NONE, mojom::MeteringMode::CONTINUOUS};
+      photo_capabilities->current_face_framing_mode =
+          (extended_camera_control->GetFlags() &
+           KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_AUTOFACEFRAMING)
+              ? mojom::MeteringMode::CONTINUOUS
+              : mojom::MeteringMode::NONE;
+    }
   }
 
   std::move(callback).Run(std::move(photo_capabilities));
@@ -1963,6 +1981,18 @@
         return;
       }
     }
+    if (settings->has_face_framing_mode) {
+      const ULONGLONG flags =
+          settings->face_framing_mode == mojom::MeteringMode::CONTINUOUS
+              ? KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_AUTOFACEFRAMING
+              : KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_MANUAL;
+      hr = SetAndCommitExtendedCameraControlFlags(
+          KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW, flags);
+      DLOG_IF_FAILED_WITH_HRESULT("Auto face framing config failed", hr);
+      if (FAILED(hr)) {
+        return;
+      }
+    }
   }
 
   std::move(callback).Run(true);
diff --git a/media/capture/video/win/video_capture_device_mf_win_unittest.cc b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
index 3de8205..aee3b0b 100644
--- a/media/capture/video/win/video_capture_device_mf_win_unittest.cc
+++ b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
@@ -292,6 +292,9 @@
       case KSPROPERTY_CAMERACONTROL_EXTENDED_BACKGROUNDSEGMENTATION:
         return (KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_OFF |
                 KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_BLUR);
+      case KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW:
+        return (KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_AUTOFACEFRAMING |
+                KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_MANUAL);
       default:
         return 0;
     }
@@ -300,6 +303,8 @@
     switch (property_id_) {
       case KSPROPERTY_CAMERACONTROL_EXTENDED_BACKGROUNDSEGMENTATION:
         return KSCAMERA_EXTENDEDPROP_BACKGROUNDSEGMENTATION_OFF;
+      case KSPROPERTY_CAMERACONTROL_EXTENDED_DIGITALWINDOW:
+        return KSCAMERA_EXTENDEDPROP_DIGITALWINDOW_MANUAL;
       default:
         return 0;
     }
@@ -1987,6 +1992,14 @@
                                 mojom::BackgroundBlurMode::BLUR),
             1);
   EXPECT_EQ(state->background_blur_mode, mojom::BackgroundBlurMode::OFF);
+
+  ASSERT_TRUE(state->supported_face_framing_modes);
+  EXPECT_EQ(2u, state->supported_face_framing_modes->size());
+  EXPECT_EQ(1, base::ranges::count(*state->supported_face_framing_modes,
+                                   mojom::MeteringMode::CONTINUOUS));
+  EXPECT_EQ(1, base::ranges::count(*state->supported_face_framing_modes,
+                                   mojom::MeteringMode::NONE));
+  EXPECT_EQ(mojom::MeteringMode::NONE, state->current_face_framing_mode);
 }
 
 // Given an |IMFCaptureSource| offering a video stream and a photo stream to
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index 07abcf3..366e51a 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -24,6 +24,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
@@ -368,8 +369,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_SYNC_NET_START,
-    &FastTransactionServer::FastNoStoreHandler,
-    nullptr,
+    base::BindRepeating(&FastTransactionServer::FastNoStoreHandler),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -573,8 +574,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    &RangeTransactionServer::RangeHandler,
-    nullptr,
+    base::BindRepeating(&RangeTransactionServer::RangeHandler),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -2237,12 +2238,13 @@
   }
 }
 
-static void PreserveRequestHeaders_Handler(const HttpRequestInfo* request,
-                                           std::string* response_status,
-                                           std::string* response_headers,
-                                           std::string* response_data) {
-  EXPECT_TRUE(request->extra_headers.HasHeader(kExtraHeaderKey));
-}
+static const auto kPreserveRequestHeaders =
+    base::BindRepeating([](const net::HttpRequestInfo* request,
+                           std::string* response_status,
+                           std::string* response_headers,
+                           std::string* response_data) {
+      EXPECT_TRUE(request->extra_headers.HasHeader(kExtraHeaderKey));
+    });
 
 // Tests that we don't remove extra headers for simple requests.
 TEST_F(HttpCacheTest, SimpleGET_PreserveRequestHeaders) {
@@ -2251,7 +2253,7 @@
     cache.disk_cache()->set_support_in_memory_entry_data(use_memory_entry_data);
 
     MockTransaction transaction(kSimpleGET_Transaction);
-    transaction.handler = PreserveRequestHeaders_Handler;
+    transaction.handler = kPreserveRequestHeaders;
     transaction.request_headers = EXTRA_HEADER;
     transaction.response_headers = "Cache-Control: max-age=0\n";
     AddMockTransaction(&transaction);
@@ -2287,7 +2289,7 @@
     RunTransactionTest(cache.http_cache(), kETagGET_Transaction);
 
     MockTransaction transaction(kETagGET_Transaction);
-    transaction.handler = PreserveRequestHeaders_Handler;
+    transaction.handler = kPreserveRequestHeaders;
     transaction.request_headers = "If-None-Match: \"foopy\"\r\n" EXTRA_HEADER;
     AddMockTransaction(&transaction);
 
@@ -6093,16 +6095,17 @@
   TestLoadTimingNetworkRequest(load_timing_info);
 }
 
-static void ETagGet_ConditionalRequest_Handler(const HttpRequestInfo* request,
-                                               std::string* response_status,
-                                               std::string* response_headers,
-                                               std::string* response_data) {
-  EXPECT_TRUE(
-      request->extra_headers.HasHeader(HttpRequestHeaders::kIfNoneMatch));
-  response_status->assign("HTTP/1.1 304 Not Modified");
-  response_headers->assign(kETagGET_Transaction.response_headers);
-  response_data->clear();
-}
+static const auto kETagGetConditionalRequestHandler =
+    base::BindRepeating([](const HttpRequestInfo* request,
+                           std::string* response_status,
+                           std::string* response_headers,
+                           std::string* response_data) {
+      EXPECT_TRUE(
+          request->extra_headers.HasHeader(HttpRequestHeaders::kIfNoneMatch));
+      response_status->assign("HTTP/1.1 304 Not Modified");
+      response_headers->assign(kETagGET_Transaction.response_headers);
+      response_data->clear();
+    });
 
 TEST_F(HttpCacheTest, ETagGET_ConditionalRequest_304) {
   MockHttpCache cache;
@@ -6119,7 +6122,7 @@
   // Get the same URL again, but this time we expect it to result
   // in a conditional request.
   transaction.load_flags = LOAD_VALIDATE_CACHE;
-  transaction.handler = ETagGet_ConditionalRequest_Handler;
+  transaction.handler = kETagGetConditionalRequestHandler;
   LoadTimingInfo load_timing_info;
   IPEndPoint remote_endpoint;
   RunTransactionTestAndGetTimingAndConnectedSocketAddress(
@@ -6137,47 +6140,41 @@
 
 class RevalidationServer {
  public:
-  RevalidationServer() {
-    s_etag_used_ = false;
-    s_last_modified_used_ = false;
+  RevalidationServer() = default;
+
+  bool EtagUsed() { return etag_used_; }
+  bool LastModifiedUsed() { return last_modified_used_; }
+
+  MockTransactionHandler GetHandlerCallback() {
+    return base::BindLambdaForTesting([this](const HttpRequestInfo* request,
+                                             std::string* response_status,
+                                             std::string* response_headers,
+                                             std::string* response_data) {
+      if (request->extra_headers.HasHeader(HttpRequestHeaders::kIfNoneMatch)) {
+        etag_used_ = true;
+      }
+
+      if (request->extra_headers.HasHeader(
+              HttpRequestHeaders::kIfModifiedSince)) {
+        last_modified_used_ = true;
+      }
+
+      if (etag_used_ || last_modified_used_) {
+        response_status->assign("HTTP/1.1 304 Not Modified");
+        response_headers->assign(kTypicalGET_Transaction.response_headers);
+        response_data->clear();
+      } else {
+        response_status->assign(kTypicalGET_Transaction.status);
+        response_headers->assign(kTypicalGET_Transaction.response_headers);
+        response_data->assign(kTypicalGET_Transaction.data);
+      }
+    });
   }
 
-  bool EtagUsed() { return s_etag_used_; }
-  bool LastModifiedUsed() { return s_last_modified_used_; }
-
-  static void Handler(const HttpRequestInfo* request,
-                      std::string* response_status,
-                      std::string* response_headers,
-                      std::string* response_data);
-
  private:
-  static bool s_etag_used_;
-  static bool s_last_modified_used_;
+  bool etag_used_ = false;
+  bool last_modified_used_ = false;
 };
-bool RevalidationServer::s_etag_used_ = false;
-bool RevalidationServer::s_last_modified_used_ = false;
-
-void RevalidationServer::Handler(const HttpRequestInfo* request,
-                                 std::string* response_status,
-                                 std::string* response_headers,
-                                 std::string* response_data) {
-  if (request->extra_headers.HasHeader(HttpRequestHeaders::kIfNoneMatch))
-      s_etag_used_ = true;
-
-  if (request->extra_headers.HasHeader(HttpRequestHeaders::kIfModifiedSince)) {
-      s_last_modified_used_ = true;
-  }
-
-  if (s_etag_used_ || s_last_modified_used_) {
-    response_status->assign("HTTP/1.1 304 Not Modified");
-    response_headers->assign(kTypicalGET_Transaction.response_headers);
-    response_data->clear();
-  } else {
-    response_status->assign(kTypicalGET_Transaction.status);
-    response_headers->assign(kTypicalGET_Transaction.response_headers);
-    response_data->assign(kTypicalGET_Transaction.data);
-  }
-}
 
 // Tests revalidation after a vary match.
 TEST_F(HttpCacheTest, GET_ValidateCache_VaryMatch) {
@@ -6197,7 +6194,7 @@
 
   // Read from the cache.
   RevalidationServer server;
-  transaction.handler = server.Handler;
+  transaction.handler = server.GetHandlerCallback();
   LoadTimingInfo load_timing_info;
   RunTransactionTestAndGetTiming(cache.http_cache(), transaction,
                                  NetLogWithSource::Make(NetLogSourceType::NONE),
@@ -6230,7 +6227,7 @@
 
   // Read from the cache and revalidate the entry.
   RevalidationServer server;
-  transaction.handler = server.Handler;
+  transaction.handler = server.GetHandlerCallback();
   transaction.request_headers = "Foo: none\r\n";
   LoadTimingInfo load_timing_info;
   RunTransactionTestAndGetTiming(cache.http_cache(), transaction,
@@ -6263,7 +6260,7 @@
 
   // Read from the cache and revalidate the entry.
   RevalidationServer server;
-  transaction.handler = server.Handler;
+  transaction.handler = server.GetHandlerCallback();
   LoadTimingInfo load_timing_info;
   RunTransactionTestAndGetTiming(cache.http_cache(), transaction,
                                  NetLogWithSource::Make(NetLogSourceType::NONE),
@@ -6295,7 +6292,7 @@
 
   // Read from the cache and don't revalidate the entry.
   RevalidationServer server;
-  transaction.handler = server.Handler;
+  transaction.handler = server.GetHandlerCallback();
   transaction.request_headers = "Foo: none\r\n";
   LoadTimingInfo load_timing_info;
   RunTransactionTestAndGetTiming(cache.http_cache(), transaction,
@@ -6482,7 +6479,8 @@
 
   // Get the same URL again, without generating a conditional request.
   transaction.load_flags = LOAD_VALIDATE_CACHE;
-  transaction.handler = ETagGet_UnconditionalRequest_Handler;
+  transaction.handler =
+      base::BindRepeating(&ETagGet_UnconditionalRequest_Handler);
   RunTransactionTest(cache.http_cache(), transaction);
 
   EXPECT_EQ(2, cache.network_layer()->transaction_count());
@@ -6505,7 +6503,8 @@
 
   // Get the same URL again, but use a byte range request.
   transaction.load_flags = LOAD_VALIDATE_CACHE;
-  transaction.handler = ETagGet_UnconditionalRequest_Handler;
+  transaction.handler =
+      base::BindRepeating(&ETagGet_UnconditionalRequest_Handler);
   transaction.request_headers = "Range: bytes = 5-\r\n";
   RunTransactionTest(cache.http_cache(), transaction);
 
@@ -6541,7 +6540,8 @@
   // Get the same URL again, but this time we expect it to result
   // in a conditional request.
   transaction.load_flags = LOAD_VALIDATE_CACHE;
-  transaction.handler = ETagGet_ConditionalRequest_NoStore_Handler;
+  transaction.handler =
+      base::BindRepeating(&ETagGet_ConditionalRequest_NoStore_Handler);
   RunTransactionTest(cache.http_cache(), transaction);
 
   EXPECT_EQ(2, cache.network_layer()->transaction_count());
@@ -8155,7 +8155,7 @@
   MockTransaction transaction(kRangeGET_TransactionOK);
   AddMockTransaction(&transaction);
   transaction.request_headers = EXTRA_HEADER;
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   RunTransactionTest(cache.http_cache(), transaction);
 
   EXPECT_EQ(1, cache.network_layer()->transaction_count());
@@ -8367,7 +8367,7 @@
   transaction.response_headers = "ETag: \"foo\"\n"
                                  "Accept-Ranges: bytes\n"
                                  "Content-Range: bytes 40-49/80\n";
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
 
   EXPECT_EQ(1, cache.network_layer()->transaction_count());
@@ -8375,7 +8375,8 @@
   EXPECT_EQ(1, cache.disk_cache()->create_count());
 
   // Now verify that there's no cached data.
-  transaction.handler = &RangeTransactionServer::RangeHandler;
+  transaction.handler =
+      base::BindRepeating(&RangeTransactionServer::RangeHandler);
   RunTransactionTestWithResponse(cache.http_cache(), kRangeGET_TransactionOK,
                                  &headers);
 
@@ -8795,7 +8796,7 @@
       "Accept-Ranges: bytes\n"
       "Content-Length: 10\n"
       "Content-Range: bytes 40-49/80\n";
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
 
   Verify206Response(headers, 40, 49);
@@ -8827,7 +8828,7 @@
       "Accept-Ranges: bytes\n"
       "Content-Length: 10\n"
       "Content-Range: bytes 40-49/80\n";
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
 
   // Two new network requests were issued, one from the cache and another after
@@ -8898,7 +8899,7 @@
       "Accept-Ranges: bytes\n"
       "Content-Length: 10\n"
       "Content-Range: bytes 40-49/80\n";
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
 
   Verify206Response(headers, 40, 49);
@@ -8936,7 +8937,7 @@
       "Accept-Ranges: bytes\n"
       "Content-Length: 10\n"
       "Content-Range: bytes 40-49/80\n";
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
 
   Verify206Response(headers, 40, 49);
@@ -9029,7 +9030,7 @@
   transaction.status = "HTTP/1.1 301 Moved Permanently";
   transaction.response_headers = "Location: http://www.bar.com/\n";
   transaction.data = "";
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
 
   // Write to the cache.
   RunTransactionTest(cache.http_cache(), transaction);
@@ -9250,7 +9251,8 @@
 
   // Try to read from the cache. This should send a network request to
   // validate it, and get a different response.
-  transaction.handler = &RangeTransactionServer::RangeHandler;
+  transaction.handler =
+      base::BindRepeating(&RangeTransactionServer::RangeHandler);
   transaction.request_headers = "Range: bytes = -30\r\n" EXTRA_HEADER;
   // Tail 30 bytes out of 80
   transaction.data = "rg: 50-59 rg: 60-69 rg: 70-79 ";
@@ -9892,7 +9894,7 @@
   std::string headers;
 
   MockTransaction transaction(kRangeGET_TransactionOK);
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   transaction.response_headers = "Content-Range: bytes 40-49/45\n"
                                  "Content-Length: 10\n";
   AddMockTransaction(&transaction);
@@ -9921,7 +9923,7 @@
   std::string headers;
 
   MockTransaction transaction(kRangeGET_TransactionOK);
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   transaction.response_headers = "Content-Range: bytes 40-49/80\n"
                                  "Content-Length: 20\n";
   AddMockTransaction(&transaction);
@@ -9951,7 +9953,7 @@
   std::string headers;
 
   MockTransaction transaction(kRangeGET_TransactionOK);
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   transaction.request_headers = "Range: bytes = 50-59\r\n" EXTRA_HEADER;
   std::string response_headers(transaction.response_headers);
   response_headers.append("Content-Range: bytes 50-59/160\n");
@@ -9991,7 +9993,7 @@
   std::string headers;
 
   MockTransaction transaction(kRangeGET_TransactionOK);
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   transaction.request_headers = "Range: bytes = 4294967288-4294967297\r\n"
                                 EXTRA_HEADER;
   transaction.response_headers =
@@ -10091,7 +10093,7 @@
   MockTransaction transaction(kRangeGET_TransactionOK);
   transaction.request_headers = "Range: bytes = 40-49\r\n" EXTRA_HEADER;
   transaction.data = "rg: 40-";  // Less than expected.
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   std::string headers(transaction.response_headers);
   headers.append("Content-Range: bytes 40-49/80\n");
   transaction.response_headers = headers.c_str();
@@ -12263,7 +12265,7 @@
 void HttpCacheHugeResourceTest::SetupPrefixSparseCacheEntry(
     MockHttpCache* cache) {
   MockTransaction transaction(kRangeGET_TransactionOK);
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   transaction.request_headers = "Range: bytes = 0-9\r\n" EXTRA_HEADER;
   transaction.response_headers =
       "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
@@ -12281,7 +12283,7 @@
 void HttpCacheHugeResourceTest::SetupInfixSparseCacheEntry(
     MockHttpCache* cache) {
   MockTransaction transaction(kRangeGET_TransactionOK);
-  transaction.handler = nullptr;
+  transaction.handler = MockTransactionHandler();
   transaction.request_headers = "Range: bytes = 99990-99999\r\n" EXTRA_HEADER;
   transaction.response_headers =
       "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
@@ -12340,8 +12342,8 @@
 
   MockTransaction transaction(kSimpleGET_Transaction);
   transaction.url = kRangeGET_TransactionOK.url;
-  transaction.handler = &LargeResourceTransactionHandler;
-  transaction.read_handler = &LargeBufferReader;
+  transaction.handler = base::BindRepeating(&LargeResourceTransactionHandler);
+  transaction.read_handler = base::BindRepeating(&LargeBufferReader);
   ScopedMockTransaction scoped_transaction(transaction);
 
   MockHttpRequest request(transaction);
@@ -12583,7 +12585,7 @@
   EXPECT_EQ(MockNetworkTransaction::kTotalReceivedBytes, received);
 
   transaction.load_flags = LOAD_VALIDATE_CACHE;
-  transaction.handler = ETagGet_ConditionalRequest_Handler;
+  transaction.handler = kETagGetConditionalRequestHandler;
   RunTransactionAndGetNetworkBytes(&cache, transaction, &sent, &received);
   EXPECT_EQ(MockNetworkTransaction::kTotalSentBytes, sent);
   EXPECT_EQ(MockNetworkTransaction::kTotalReceivedBytes, received);
@@ -12607,7 +12609,8 @@
   EXPECT_EQ(MockNetworkTransaction::kTotalReceivedBytes, received);
 
   RevalidationServer server;
-  transaction.handler = server.Handler;
+  transaction.handler = server.GetHandlerCallback();
+
   transaction.request_headers = "Foo: none\r\n";
   RunTransactionAndGetNetworkBytes(&cache, transaction, &sent, &received);
   EXPECT_EQ(MockNetworkTransaction::kTotalSentBytes, sent);
@@ -13208,7 +13211,7 @@
 
   ScopedMockTransaction still_valid(kETagGET_Transaction);
   still_valid.load_flags = LOAD_VALIDATE_CACHE;  // Force a validation.
-  still_valid.handler = ETagGet_ConditionalRequest_Handler;
+  still_valid.handler = kETagGetConditionalRequestHandler;
 
   HttpResponseInfo response_info;
   RunTransactionTestWithResponseInfo(cache.http_cache(), still_valid,
diff --git a/net/http/http_transaction_test_util.cc b/net/http/http_transaction_test_util.cc
index 4b1835da..4a4ecea 100644
--- a/net/http/http_transaction_test_util.cc
+++ b/net/http/http_transaction_test_util.cc
@@ -66,8 +66,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -90,8 +90,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -115,8 +115,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -140,8 +140,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -164,8 +164,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -376,7 +376,7 @@
 
   if (OK == num) {
     if (read_handler_) {
-      num = (*read_handler_)(content_length_, data_cursor_, buf, buf_len);
+      num = read_handler_.Run(content_length_, data_cursor_, buf, buf_len);
       data_cursor_ += num;
     } else {
       int data_len = static_cast<int>(data_.size());
@@ -501,10 +501,10 @@
     HttpRequestInfo new_request = *request;
     modify_request_headers_callback_.Run(&new_request.extra_headers);
     if (t->handler) {
-      (t->handler)(&new_request, &resp_status, &resp_headers, &resp_data);
+      t->handler.Run(&new_request, &resp_status, &resp_headers, &resp_data);
     }
   } else if (t->handler) {
-    (t->handler)(request, &resp_status, &resp_headers, &resp_data);
+    t->handler.Run(request, &resp_status, &resp_headers, &resp_data);
   }
   if (t->read_handler)
     read_handler_ = t->read_handler;
diff --git a/net/http/http_transaction_test_util.h b/net/http/http_transaction_test_util.h
index cf8473d..745aa296 100644
--- a/net/http/http_transaction_test_util.h
+++ b/net/http/http_transaction_test_util.h
@@ -61,14 +61,14 @@
   TEST_MODE_SLOW_READ = 1 << 5
 };
 
-using MockTransactionReadHandler = int (*)(int64_t content_length,
-                                           int64_t offset,
-                                           IOBuffer* buf,
-                                           int buf_len);
-using MockTransactionHandler = void (*)(const HttpRequestInfo* request,
-                                        std::string* response_status,
-                                        std::string* response_headers,
-                                        std::string* response_data);
+using MockTransactionReadHandler = base::RepeatingCallback<
+    int(int64_t content_length, int64_t offset, IOBuffer* buf, int buf_len)>;
+
+using MockTransactionHandler =
+    base::RepeatingCallback<void(const HttpRequestInfo* request,
+                                 std::string* response_status,
+                                 std::string* response_headers,
+                                 std::string* response_data)>;
 
 // Default TransportInfo suitable for most MockTransactions.
 // Describes a direct connection to (127.0.0.1, 80).
@@ -299,7 +299,7 @@
   int64_t content_length_ = 0;
   int test_mode_;
   RequestPriority priority_;
-  MockTransactionReadHandler read_handler_ = nullptr;
+  MockTransactionReadHandler read_handler_;
   raw_ptr<CreateHelper> websocket_handshake_stream_create_helper_ = nullptr;
   BeforeNetworkStartCallback before_network_start_callback_;
   ConnectedCallback connected_callback_;
diff --git a/net/ssl/ssl_client_auth_cache.cc b/net/ssl/ssl_client_auth_cache.cc
index b4e1aec6..daa91cc 100644
--- a/net/ssl/ssl_client_auth_cache.cc
+++ b/net/ssl/ssl_client_auth_cache.cc
@@ -46,19 +46,21 @@
 }
 
 base::flat_set<HostPortPair> SSLClientAuthCache::GetCachedServers() const {
-  // TODO(mattm): in c++20 maybe could avoid the intermediate vector by using:
+  // TODO(mattm): If views become permitted by Chromium style maybe we could
+  // avoid the intermediate vector by using:
   // auto keys = std::views::keys(m);
   // base::flat_set<HostPortPair>(base::sorted_unique, keys.begin(),
   //                              keys.end());
 
-  std::vector<HostPortPair> keys;
+  // Use the flat_set underlying container type (currently a std::vector), so we
+  // can move the keys into the set instead of copying them.
+  base::flat_set<HostPortPair>::container_type keys;
   keys.reserve(cache_.size());
   for (const auto& [key, _] : cache_) {
     keys.push_back(key);
   }
   // `cache_` is a std::map, so the keys are already sorted.
-  return base::flat_set<HostPortPair>(base::sorted_unique, keys.begin(),
-                                      keys.end());
+  return base::flat_set<HostPortPair>(base::sorted_unique, std::move(keys));
 }
 
 }  // namespace net
diff --git a/net/url_request/url_request_job_unittest.cc b/net/url_request/url_request_job_unittest.cc
index 536c234..d9501c6 100644
--- a/net/url_request/url_request_job_unittest.cc
+++ b/net/url_request/url_request_job_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/functional/bind.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
@@ -93,8 +94,8 @@
   transaction->data = "hello";
   transaction->dns_aliases = {};
   transaction->test_mode = TEST_MODE_NORMAL;
-  transaction->handler = nullptr;
-  transaction->read_handler = nullptr;
+  transaction->handler = MockTransactionHandler();
+  transaction->read_handler = MockTransactionReadHandler();
   if (GURL(original_url).SchemeIsCryptographic()) {
     transaction->cert =
         net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
@@ -122,8 +123,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     OK,
@@ -146,8 +147,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     OK,
@@ -171,8 +172,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    &GZipServer,
-    nullptr,
+    base::BindRepeating(&GZipServer),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -196,8 +197,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_SLOW_READ,
-    &GZipHelloServer,
-    nullptr,
+    base::BindRepeating(&GZipHelloServer),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -222,8 +223,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -246,8 +247,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -271,8 +272,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_NORMAL,
-    nullptr,
-    nullptr,
+    MockTransactionHandler(),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -297,8 +298,8 @@
     absl::nullopt,
     absl::nullopt,
     TEST_MODE_SLOW_READ,
-    &BrotliHelloServer,
-    nullptr,
+    base::BindRepeating(&BrotliHelloServer),
+    MockTransactionReadHandler(),
     nullptr,
     0,
     0,
@@ -443,7 +444,7 @@
                              TRAFFIC_ANNOTATION_FOR_TESTS));
   MockTransaction transaction(kGZipTransaction);
   transaction.test_mode = TEST_MODE_SYNC_ALL | TEST_MODE_SLOW_READ;
-  transaction.handler = &BigGZipServer;
+  transaction.handler = base::BindRepeating(&BigGZipServer);
   AddMockTransaction(&transaction);
 
   req->set_method("GET");
diff --git a/sandbox/policy/win/sandbox_win.cc b/sandbox/policy/win/sandbox_win.cc
index 5eb2c633..8a49455 100644
--- a/sandbox/policy/win/sandbox_win.cc
+++ b/sandbox/policy/win/sandbox_win.cc
@@ -1146,7 +1146,8 @@
 #if defined(ARCH_CPU_64_BITS)
   size_t memory_limit = static_cast<size_t>(kDataSizeLimit);
 
-  if (sandbox_type == Sandbox::kGpu || sandbox_type == Sandbox::kRenderer) {
+  if (sandbox_type == Sandbox::kGpu || sandbox_type == Sandbox::kRenderer ||
+      sandbox_type == Sandbox::kOnDeviceModelExecution) {
     constexpr uint64_t GB = 1024 * 1024 * 1024;
     // Allow the GPU/RENDERER process's sandbox to access more physical memory
     // if it's available on the system.
diff --git a/services/network/shared_dictionary/shared_dictionary_network_transaction_unittest.cc b/services/network/shared_dictionary/shared_dictionary_network_transaction_unittest.cc
index 3200f1d3..b7b6bc62 100644
--- a/services/network/shared_dictionary/shared_dictionary_network_transaction_unittest.cc
+++ b/services/network/shared_dictionary/shared_dictionary_network_transaction_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "services/network/shared_dictionary/shared_dictionary_network_transaction.h"
 
+#include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/test/scoped_feature_list.h"
 #include "crypto/secure_hash.h"
@@ -219,15 +220,15 @@
   *response_data = kZstdEncodedDataString;
 }
 
-static void TestTransactionHandlerWithoutAvailableDictionary(
-    const net::HttpRequestInfo* request,
-    std::string* response_status,
-    std::string* response_headers,
-    std::string* response_data) {
-  EXPECT_FALSE(request->extra_headers.HasHeader(
-      network::shared_dictionary::kSecAvailableDictionaryHeaderName));
-  *response_data = kTestData;
-}
+static const auto kTestTransactionHandlerWithoutAvailableDictionary =
+    base::BindRepeating([](const net::HttpRequestInfo* request,
+                           std::string* response_status,
+                           std::string* response_headers,
+                           std::string* response_data) {
+      EXPECT_FALSE(request->extra_headers.HasHeader(
+          network::shared_dictionary::kSecAvailableDictionaryHeaderName));
+      *response_data = kTestData;
+    });
 
 const net::MockTransaction kBrotliDictionaryTestTransaction = {
     .url = "https://test.example/test",
@@ -244,8 +245,8 @@
     .fps_cache_filter = absl::nullopt,
     .browser_run_id = absl::nullopt,
     .test_mode = net::TEST_MODE_NORMAL,
-    .handler = BrotliTestTransactionHandler,
-    .read_handler = nullptr,
+    .handler = base::BindRepeating(&BrotliTestTransactionHandler),
+    .read_handler = net::MockTransactionReadHandler(),
     .cert = nullptr,
     .cert_status = 0,
     .ssl_connection_status = 0,
@@ -268,8 +269,8 @@
     .fps_cache_filter = absl::nullopt,
     .browser_run_id = absl::nullopt,
     .test_mode = net::TEST_MODE_NORMAL,
-    .handler = ZstdTestTransactionHandler,
-    .read_handler = nullptr,
+    .handler = base::BindRepeating(&ZstdTestTransactionHandler),
+    .read_handler = net::MockTransactionReadHandler(),
     .cert = nullptr,
     .cert_status = 0,
     .ssl_connection_status = 0,
@@ -345,7 +346,7 @@
   // header.
   net::MockTransaction new_mock_transaction = kBrotliDictionaryTestTransaction;
   new_mock_transaction.handler =
-      TestTransactionHandlerWithoutAvailableDictionary;
+      kTestTransactionHandlerWithoutAvailableDictionary;
   net::AddMockTransaction(&new_mock_transaction);
 
   net::MockHttpRequest request(new_mock_transaction);
@@ -379,7 +380,7 @@
   // header.
   net::MockTransaction new_mock_transaction = kBrotliDictionaryTestTransaction;
   new_mock_transaction.handler =
-      TestTransactionHandlerWithoutAvailableDictionary;
+      kTestTransactionHandlerWithoutAvailableDictionary;
   net::AddMockTransaction(&new_mock_transaction);
 
   net::MockHttpRequest request(new_mock_transaction);
@@ -414,7 +415,7 @@
   // header.
   net::MockTransaction new_mock_transaction = kBrotliDictionaryTestTransaction;
   new_mock_transaction.handler =
-      TestTransactionHandlerWithoutAvailableDictionary;
+      kTestTransactionHandlerWithoutAvailableDictionary;
   net::AddMockTransaction(&new_mock_transaction);
 
   net::MockHttpRequest request(new_mock_transaction);
@@ -448,7 +449,7 @@
   // header.
   net::MockTransaction new_mock_transaction = kBrotliDictionaryTestTransaction;
   new_mock_transaction.handler =
-      TestTransactionHandlerWithoutAvailableDictionary;
+      kTestTransactionHandlerWithoutAvailableDictionary;
   net::AddMockTransaction(&new_mock_transaction);
 
   net::MockHttpRequest request(new_mock_transaction);
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index b1c9b62..2756d8d 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -8550,6 +8550,21 @@
             ]
         }
     ],
+    "IOSFullscreenImprovement": [
+        {
+            "platforms": [
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "FullscreenImprovement"
+                    ]
+                }
+            ]
+        }
+    ],
     "IOSGhostCards": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index 8ae36a9..6d50433 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 8ae36a93bedcb867cb8663c72751bed9c88af68b
+Subproject commit 6d50433c432b59a77773ccd2b757f09858a51093
diff --git a/third_party/blink/public/common/service_worker/service_worker_router_rule.h b/third_party/blink/public/common/service_worker/service_worker_router_rule.h
index f034efe2..498e2eeb 100644
--- a/third_party/blink/public/common/service_worker/service_worker_router_rule.h
+++ b/third_party/blink/public/common/service_worker/service_worker_router_rule.h
@@ -24,6 +24,8 @@
 
 // TODO(crbug.com/1490445): set this value by discussing in spec proposal.
 static constexpr int kServiceWorkerRouterConditionMaxRecursionDepth = 10;
+// TODO(crbug.com/1503017): set this value by discussing in spec proposal.
+static constexpr size_t kServiceWorkerMaxRouterSize = 256;
 
 struct ServiceWorkerRouterRequestCondition {
   // https://fetch.spec.whatwg.org/#concept-request-method
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
index 5429658..bf99c180 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -1091,7 +1091,8 @@
   builder.Append(value.CustomCSSText());
   builder.Append(")");
 
-  NOTREACHED() << builder.ToString();
+  LOG(DFATAL) << builder.ToString();
+  NOTREACHED();
   return cssvalue::CSSUnsetValue::Create();
 }
 
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
index a22e3e2..69e9012 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
@@ -2727,306 +2727,6 @@
          "border-width";
 }
 
-TEST_F(StyleResolverTest, PositionFallbackStylesBasic) {
-  ScopedCSSAnchorPositioningForTest enabled(true);
-
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      @position-fallback --fallback {
-        @try { left: 100px; }
-        @try { top: 100px; }
-        @try { inset: 50px; }
-      }
-      #target {
-        position: absolute;
-        position-fallback: --fallback;
-      }
-    </style>
-    <div id="target"></div>
-  )HTML");
-
-  UpdateAllLifecyclePhasesForTest();
-
-  Element* target = GetElementById("target");
-  const ComputedStyle* base_style = target->GetComputedStyle();
-  ASSERT_TRUE(base_style);
-  EXPECT_EQ(Length::Auto(), GetTop(*base_style));
-  EXPECT_EQ(Length::Auto(), GetLeft(*base_style));
-
-  const ComputedStyle* try1 = target->StyleForPositionFallback(0);
-  ASSERT_TRUE(try1);
-  EXPECT_EQ(Length::Auto(), GetTop(*try1));
-  EXPECT_EQ(Length::Fixed(100), GetLeft(*try1));
-
-  const ComputedStyle* try2 = target->StyleForPositionFallback(1);
-  ASSERT_TRUE(try2);
-  EXPECT_EQ(Length::Fixed(100), GetTop(*try2));
-  EXPECT_EQ(Length::Auto(), GetLeft(*try2));
-
-  // Shorthand should also work
-  const ComputedStyle* try3 = target->StyleForPositionFallback(2);
-  ASSERT_TRUE(try3);
-  EXPECT_EQ(Length::Fixed(50), GetTop(*try3));
-  EXPECT_EQ(Length::Fixed(50), GetLeft(*try3));
-  EXPECT_EQ(Length::Fixed(50), GetBottom(*try3));
-  EXPECT_EQ(Length::Fixed(50), GetRight(*try3));
-
-  // Returns nullptr when index is out of bound.
-  EXPECT_FALSE(target->StyleForPositionFallback(3));
-}
-
-TEST_F(StyleResolverTest, PositionFallbackNameInvalid) {
-  ScopedCSSAnchorPositioningForTest enabled(true);
-
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      #target {
-        position: absolute;
-        position-fallback: --invalid;
-      }
-    </style>
-    <div id="target"></div>
-  )HTML");
-
-  UpdateAllLifecyclePhasesForTest();
-
-  Element* target = GetElementById("target");
-  EXPECT_FALSE(target->StyleForPositionFallback(0));
-}
-
-TEST_F(StyleResolverTest, PositionFallbackStylesResolveLogicalProperties) {
-  ScopedCSSAnchorPositioningForTest enabled(true);
-
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      @position-fallback --fallback {
-        @try { inset-inline-start: 100px; }
-        @try { inset-block: 100px 90px; }
-      }
-      #target {
-        position: absolute;
-        writing-mode: vertical-rl;
-        direction: rtl;
-        inset: 50px;
-        position-fallback: --fallback;
-      }
-    </style>
-    <div id="target"></div>
-  )HTML");
-
-  UpdateAllLifecyclePhasesForTest();
-
-  Element* target = GetElementById("target");
-  const ComputedStyle* base_style = target->GetComputedStyle();
-  ASSERT_TRUE(base_style);
-  EXPECT_EQ(Length::Fixed(50), GetTop(*base_style));
-  EXPECT_EQ(Length::Fixed(50), GetLeft(*base_style));
-  EXPECT_EQ(Length::Fixed(50), GetBottom(*base_style));
-  EXPECT_EQ(Length::Fixed(50), GetRight(*base_style));
-
-  // 'inset-inline-start' should resolve to 'bottom'
-  const ComputedStyle* try1 = target->StyleForPositionFallback(0);
-  ASSERT_TRUE(try1);
-  EXPECT_EQ(Length::Fixed(50), GetTop(*try1));
-  EXPECT_EQ(Length::Fixed(50), GetLeft(*try1));
-  EXPECT_EQ(Length::Fixed(100), GetBottom(*try1));
-  EXPECT_EQ(Length::Fixed(50), GetRight(*try1));
-
-  // 'inset-block' with two parameters should set 'right' and then 'left'
-  const ComputedStyle* try2 = target->StyleForPositionFallback(1);
-  ASSERT_TRUE(try2);
-  EXPECT_EQ(Length::Fixed(50), GetTop(*try2));
-  EXPECT_EQ(Length::Fixed(90), GetLeft(*try2));
-  EXPECT_EQ(Length::Fixed(50), GetBottom(*try2));
-  EXPECT_EQ(Length::Fixed(100), GetRight(*try2));
-
-  EXPECT_FALSE(target->StyleForPositionFallback(2));
-}
-
-TEST_F(StyleResolverTest, PositionFallbackStylesResolveRelativeLengthUnits) {
-  ScopedCSSAnchorPositioningForTest enabled(true);
-
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      @position-fallback --fallback {
-        @try { top: 2em; }
-      }
-      #target {
-        position: absolute;
-        font-size: 20px;
-        position-fallback: --fallback;
-      }
-    </style>
-    <div id="target"></div>
-  )HTML");
-
-  UpdateAllLifecyclePhasesForTest();
-
-  Element* target = GetElementById("target");
-  const ComputedStyle* base_style = target->GetComputedStyle();
-  ASSERT_TRUE(base_style);
-  EXPECT_EQ(Length::Auto(), GetTop(*base_style));
-
-  // '2em' should resolve to '40px'
-  const ComputedStyle* try1 = target->StyleForPositionFallback(0);
-  ASSERT_TRUE(try1);
-  EXPECT_EQ(Length::Fixed(40), GetTop(*try1));
-
-  EXPECT_FALSE(target->StyleForPositionFallback(1));
-}
-
-TEST_F(StyleResolverTest, PositionFallbackStylesInBeforePseudoElement) {
-  ScopedCSSAnchorPositioningForTest enabled(true);
-
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      @position-fallback --fallback {
-        @try { top: 50px; }
-      }
-      #target::before {
-        display: block;
-        content: 'before';
-        position: absolute;
-        position-fallback: --fallback;
-      }
-    </style>
-    <div id="target"></div>
-  )HTML");
-
-  UpdateAllLifecyclePhasesForTest();
-
-  Element* target = GetElementById("target");
-  Element* before = target->GetPseudoElement(kPseudoIdBefore);
-  ASSERT_TRUE(before);
-
-  const ComputedStyle* base_style = before->GetComputedStyle();
-  ASSERT_TRUE(base_style);
-  EXPECT_EQ(Length::Auto(), GetTop(*base_style));
-
-  // 'position-fallback' applies to ::before pseudo-element.
-  const ComputedStyle* try1 = before->StyleForPositionFallback(0);
-  ASSERT_TRUE(try1);
-  EXPECT_EQ(Length::Fixed(50), GetTop(*try1));
-
-  EXPECT_FALSE(before->StyleForPositionFallback(1));
-}
-
-TEST_F(StyleResolverTest, PositionFallbackStylesCSSWideKeywords) {
-  ScopedCSSAnchorPositioningForTest enabled(true);
-
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      @position-fallback --fallback {
-        @try { top: initial }
-        @try { left: inherit }
-        @try { right: unset }
-        /* 'revert' and 'revert-layer' are already rejected by parser */
-      }
-      #target {
-        position: absolute;
-        inset: 50px;
-        position-fallback: --fallback;
-      }
-      #container {
-        position: absolute;
-        inset: 100px;
-      }
-    </style>
-    <div id="container">
-      <div id="target"></div>
-    </div>
-  )HTML");
-
-  UpdateAllLifecyclePhasesForTest();
-
-  Element* target = GetElementById("target");
-  const ComputedStyle* base_style = target->GetComputedStyle();
-  ASSERT_TRUE(base_style);
-  EXPECT_EQ(Length::Fixed(50), GetTop(*base_style));
-  EXPECT_EQ(Length::Fixed(50), GetLeft(*base_style));
-  EXPECT_EQ(Length::Fixed(50), GetBottom(*base_style));
-  EXPECT_EQ(Length::Fixed(50), GetRight(*base_style));
-
-  const ComputedStyle* try1 = target->StyleForPositionFallback(0);
-  ASSERT_TRUE(try1);
-  EXPECT_EQ(Length::Auto(), GetTop(*try1));
-  EXPECT_EQ(Length::Fixed(50), GetLeft(*try1));
-  EXPECT_EQ(Length::Fixed(50), GetBottom(*try1));
-  EXPECT_EQ(Length::Fixed(50), GetRight(*try1));
-
-  const ComputedStyle* try2 = target->StyleForPositionFallback(1);
-  ASSERT_TRUE(try2);
-  EXPECT_EQ(Length::Fixed(50), GetTop(*try2));
-  EXPECT_EQ(Length::Fixed(100), GetLeft(*try2));
-  EXPECT_EQ(Length::Fixed(50), GetBottom(*try2));
-  EXPECT_EQ(Length::Fixed(50), GetRight(*try2));
-
-  const ComputedStyle* try3 = target->StyleForPositionFallback(2);
-  ASSERT_TRUE(try3);
-  EXPECT_EQ(Length::Fixed(50), GetTop(*try3));
-  EXPECT_EQ(Length::Fixed(50), GetLeft(*try3));
-  EXPECT_EQ(Length::Fixed(50), GetBottom(*try3));
-  EXPECT_EQ(Length::Auto(), GetRight(*try3));
-
-  EXPECT_FALSE(target->StyleForPositionFallback(3));
-}
-
-TEST_F(StyleResolverTest, PositionFallbackPropertyValueChange) {
-  ScopedCSSAnchorPositioningForTest enabled(true);
-
-  SetBodyInnerHTML(R"HTML(
-    <style>
-      @position-fallback --foo {
-        @try { top: 100px }
-      }
-      @position-fallback --bar {
-        @try { left: 100px }
-      }
-      #target {
-        position: absolute;
-        position-fallback: --foo;
-      }
-    </style>
-    <div id="target"></div>
-  )HTML");
-
-  UpdateAllLifecyclePhasesForTest();
-
-  Element* target = GetElementById("target");
-
-  {
-    const ComputedStyle* base_style = target->GetComputedStyle();
-    ASSERT_TRUE(base_style);
-    EXPECT_EQ(Length::Auto(), GetTop(*base_style));
-    EXPECT_EQ(Length::Auto(), GetLeft(*base_style));
-
-    const ComputedStyle* fallback = target->StyleForPositionFallback(0);
-    ASSERT_TRUE(fallback);
-    EXPECT_EQ(Length::Fixed(100), GetTop(*fallback));
-    EXPECT_EQ(Length::Auto(), GetLeft(*fallback));
-
-    EXPECT_FALSE(target->StyleForPositionFallback(1));
-  }
-
-  target->SetInlineStyleProperty(CSSPropertyID::kPositionFallback, "--bar");
-  UpdateAllLifecyclePhasesForTest();
-
-  {
-    const ComputedStyle* base_style = target->GetComputedStyle();
-    ASSERT_TRUE(base_style);
-    EXPECT_EQ(Length::Auto(), GetTop(*base_style));
-    EXPECT_EQ(Length::Auto(), GetLeft(*base_style));
-
-    const ComputedStyle* fallback = target->StyleForPositionFallback(0);
-    ASSERT_TRUE(fallback);
-    ASSERT_TRUE(fallback);
-    EXPECT_EQ(Length::Auto(), GetTop(*fallback));
-    EXPECT_EQ(Length::Fixed(100), GetLeft(*fallback));
-
-    EXPECT_FALSE(target->StyleForPositionFallback(1));
-  }
-}
-
 TEST_F(StyleResolverTest, PositionFallbackStylesBasic_Cascade) {
   ScopedCSSAnchorPositioningForTest enabled(true);
   ScopedCSSAnchorPositioningCascadeFallbackForTest cascade(true);
@@ -3682,59 +3382,6 @@
   EXPECT_FALSE(c->ComputedStyleRef().CanAffectAnimations());
 }
 
-TEST_F(StyleResolverTest, HasAutoAnchorPositioning) {
-  GetDocument().documentElement()->setInnerHTML(R"HTML(
-    <style>
-      #target {
-        position: absolute;
-        position-fallback: --pf;
-        left: anchor(auto);
-      }
-      @position-fallback --pf {
-        @try { width: 100px; }
-        @try { top: anchor(auto); }
-        @try { left: anchor(auto); }
-        @try { left: anchor(auto); top: anchor(auto); }
-      }
-    </style>
-    <div id="target"></div>
-  )HTML");
-
-  UpdateAllLifecyclePhasesForTest();
-
-  Element* target = GetDocument().getElementById(AtomicString("target"));
-
-  const ComputedStyle& base_style = target->ComputedStyleRef();
-  EXPECT_TRUE(base_style.HasAutoAnchorPositioning());
-  EXPECT_FALSE(base_style.HasAutoAnchorPositioningInXAxisFromTryBlock());
-  EXPECT_FALSE(base_style.HasAutoAnchorPositioningInYAxisFromTryBlock());
-
-  // First @try block doesn't have any auto anchor positioning in it.
-  const ComputedStyle& fallback1 = *target->StyleForPositionFallback(0);
-  EXPECT_TRUE(fallback1.HasAutoAnchorPositioning());
-  EXPECT_FALSE(fallback1.HasAutoAnchorPositioningInXAxisFromTryBlock());
-  EXPECT_FALSE(fallback1.HasAutoAnchorPositioningInYAxisFromTryBlock());
-
-  // Second @try block has auto anchor positioning only in y axis.
-  const ComputedStyle& fallback2 = *target->StyleForPositionFallback(1);
-  EXPECT_TRUE(fallback2.HasAutoAnchorPositioning());
-  EXPECT_FALSE(fallback2.HasAutoAnchorPositioningInXAxisFromTryBlock());
-  EXPECT_TRUE(fallback2.HasAutoAnchorPositioningInYAxisFromTryBlock());
-
-  // Third @try block has auto anchor positioning only in x axis, even if the
-  // resolved computed style is equal to the base style.
-  const ComputedStyle& fallback3 = *target->StyleForPositionFallback(2);
-  EXPECT_TRUE(fallback3.HasAutoAnchorPositioning());
-  EXPECT_TRUE(fallback3.HasAutoAnchorPositioningInXAxisFromTryBlock());
-  EXPECT_FALSE(fallback3.HasAutoAnchorPositioningInYAxisFromTryBlock());
-
-  // Fourth @try block has auto anchor positioning in both axes.
-  const ComputedStyle& fallback4 = *target->StyleForPositionFallback(3);
-  EXPECT_TRUE(fallback4.HasAutoAnchorPositioning());
-  EXPECT_TRUE(fallback4.HasAutoAnchorPositioningInXAxisFromTryBlock());
-  EXPECT_TRUE(fallback4.HasAutoAnchorPositioningInYAxisFromTryBlock());
-}
-
 TEST_F(StyleResolverTest, CssRulesForElementExcludeStartingStyle) {
   SetBodyInnerHTML(R"HTML(
     <style>
diff --git a/third_party/blink/renderer/core/style/style_non_inherited_variables.h b/third_party/blink/renderer/core/style/style_non_inherited_variables.h
index 8720ba7..b86d783 100644
--- a/third_party/blink/renderer/core/style/style_non_inherited_variables.h
+++ b/third_party/blink/renderer/core/style/style_non_inherited_variables.h
@@ -58,10 +58,20 @@
   const StyleVariables::DataMap& Data() const { return variables_.Data(); }
   const StyleVariables::ValueMap& Values() const { return variables_.Values(); }
 
+  friend CORE_EXPORT std::ostream& operator<<(
+      std::ostream& stream,
+      const StyleNonInheritedVariables& variables);
+
  private:
   StyleVariables variables_;
 };
 
+inline CORE_EXPORT std::ostream& operator<<(
+    std::ostream& stream,
+    const StyleNonInheritedVariables& variables) {
+  return stream << variables.variables_;
+}
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_STYLE_STYLE_NON_INHERITED_VARIABLES_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/transceiver_state_surfacer.cc b/third_party/blink/renderer/modules/peerconnection/transceiver_state_surfacer.cc
index 997ac5fa..a149d9c 100644
--- a/third_party/blink/renderer/modules/peerconnection/transceiver_state_surfacer.cc
+++ b/third_party/blink/renderer/modules/peerconnection/transceiver_state_surfacer.cc
@@ -27,8 +27,7 @@
     scoped_refptr<base::SingleThreadTaskRunner> signaling_task_runner)
     : main_task_runner_(std::move(main_task_runner)),
       signaling_task_runner_(std::move(signaling_task_runner)),
-      is_initialized_(false),
-      states_obtained_(false) {
+      is_initialized_(false) {
   DCHECK(main_task_runner_);
   DCHECK(signaling_task_runner_);
 }
@@ -38,7 +37,6 @@
     : main_task_runner_(other.main_task_runner_),
       signaling_task_runner_(other.signaling_task_runner_),
       is_initialized_(other.is_initialized_),
-      states_obtained_(other.states_obtained_),
       sctp_transport_snapshot_(other.sctp_transport_snapshot_),
       transceiver_states_(std::move(other.transceiver_states_)) {
   // Explicitly null |other|'s task runners for use in destructor.
@@ -52,19 +50,6 @@
   DCHECK(!main_task_runner_ || main_task_runner_->BelongsToCurrentThread());
 }
 
-TransceiverStateSurfacer& TransceiverStateSurfacer::operator=(
-    TransceiverStateSurfacer&& other) {
-  main_task_runner_ = other.main_task_runner_;
-  signaling_task_runner_ = other.signaling_task_runner_;
-  states_obtained_ = other.states_obtained_;
-  sctp_transport_snapshot_ = other.sctp_transport_snapshot_;
-  transceiver_states_ = std::move(other.transceiver_states_);
-  // Explicitly null |other|'s task runners for use in destructor.
-  other.main_task_runner_ = nullptr;
-  other.signaling_task_runner_ = nullptr;
-  return *this;
-}
-
 void TransceiverStateSurfacer::Initialize(
     rtc::scoped_refptr<webrtc::PeerConnectionInterface> native_peer_connection,
     scoped_refptr<blink::WebRtcMediaStreamTrackAdapterMap> track_adapter_map,
@@ -150,7 +135,6 @@
   DCHECK(is_initialized_);
   for (auto& transceiver_state : transceiver_states_)
     transceiver_state.Initialize();
-  states_obtained_ = true;
   return std::move(transceiver_states_);
 }
 
diff --git a/third_party/blink/renderer/modules/peerconnection/transceiver_state_surfacer.h b/third_party/blink/renderer/modules/peerconnection/transceiver_state_surfacer.h
index 7fe89688..5c2ee02 100644
--- a/third_party/blink/renderer/modules/peerconnection/transceiver_state_surfacer.h
+++ b/third_party/blink/renderer/modules/peerconnection/transceiver_state_surfacer.h
@@ -34,10 +34,7 @@
   TransceiverStateSurfacer(const TransceiverStateSurfacer&) = delete;
   ~TransceiverStateSurfacer();
 
-  // This is intended to be used for moving the object from the signaling thread
-  // to the main thread and as such has no thread checks. Once moved to the main
-  // this should only be invoked on the main thread.
-  TransceiverStateSurfacer& operator=(TransceiverStateSurfacer&&);
+  TransceiverStateSurfacer& operator=(TransceiverStateSurfacer&&) = delete;
   TransceiverStateSurfacer& operator=(const TransceiverStateSurfacer&) = delete;
 
   bool is_initialized() const { return is_initialized_; }
@@ -58,7 +55,6 @@
   scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> signaling_task_runner_;
   bool is_initialized_;
-  bool states_obtained_;
   blink::WebRTCSctpTransportSnapshot sctp_transport_snapshot_;
   std::vector<blink::RtpTransceiverState> transceiver_states_
       ALLOW_DISCOURAGED_TYPE(
diff --git a/third_party/blink/renderer/modules/service_worker/install_event.cc b/third_party/blink/renderer/modules/service_worker/install_event.cc
index 09451d9..3613ec2 100644
--- a/third_party/blink/renderer/modules/service_worker/install_event.cc
+++ b/third_party/blink/renderer/modules/service_worker/install_event.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/modules/service_worker/install_event.h"
 
+#include "third_party/blink/public/common/service_worker/service_worker_router_rule.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_router_rule.mojom-blink.h"
 #include "third_party/blink/public/platform/web_security_origin.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
@@ -162,6 +163,11 @@
     rules.rules.emplace_back(*r);
   } else {
     CHECK(v8_rules->IsRouterRuleSequence());
+    if (v8_rules->GetAsRouterRuleSequence().size() >=
+        kServiceWorkerMaxRouterSize) {
+      exception_state.ThrowTypeError("Too many router rules.");
+      return;
+    }
     for (const blink::RouterRule* rule : v8_rules->GetAsRouterRuleSequence()) {
       auto r = ConvertV8RouterRuleToBlink(rule, base_url, exception_state);
       if (!r) {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 4bbc1fe3..dedc43e 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -796,6 +796,7 @@
       // Use full cascading for applying @try fallback for anchor positioning
       // during layout.
       name: "CSSAnchorPositioningCascadeFallback",
+      implied_by: ["CSSAnchorPositioning"],
     },
     {
       name: "CSSAnimationComposition",
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 579ad7b..b32d69d6 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1527,7 +1527,6 @@
 crbug.com/1488371 virtual/deprecate-unload/http/tests/devtools/sources/debugger/skip-pause-during-navigation.js [ Skip Failure Timeout ]
 crbug.com/1488371 virtual/deprecate-unload/http/tests/history/history-replace-updates-current-item.html [ Skip Failure Timeout ]
 crbug.com/1488371 virtual/deprecate-unload/http/tests/inspector-protocol/permissions-policy.js [ Skip Failure Timeout ]
-crbug.com/1488371 virtual/deprecate-unload/http/tests/navigation/history-back-across-form-submission-to-fragment.html [ Skip Failure Timeout ]
 crbug.com/1488371 virtual/deprecate-unload/http/tests/navigation/image-css-load-in-subframe-unload-handler.html [ Skip Failure Timeout ]
 crbug.com/1488371 virtual/deprecate-unload/http/tests/navigation/image-load-in-subframe-unload-handler.html [ Skip Failure Timeout ]
 crbug.com/1488371 virtual/deprecate-unload/http/tests/navigation/image-load-in-unload-handler.html [ Skip Failure Timeout ]
@@ -2706,9 +2705,9 @@
 
 # ====== New tests from wpt-importer added here ======
 crbug.com/626703 [ Linux ] external/wpt/css/css-backgrounds/background-image-007.html [ Failure ]
-crbug.com/626703 [ Mac11 ] external/wpt/fetch/private-network-access/window-open.tentative.https.window.html [ Timeout ]
+crbug.com/626703 [ Linux ] external/wpt/fetch/private-network-access/window-open.tentative.https.window.html [ Timeout ]
 crbug.com/626703 [ Mac11 ] external/wpt/font-access/font_access_basic.tentative.https.window.html [ Timeout ]
-crbug.com/626703 [ Mac11 ] virtual/pna-workers-enabled/external/wpt/fetch/private-network-access/window-open.tentative.https.window.html [ Timeout ]
+crbug.com/626703 [ Linux ] virtual/pna-workers-enabled/external/wpt/fetch/private-network-access/window-open.tentative.https.window.html [ Timeout ]
 crbug.com/626703 external/wpt/screen-orientation/lock-unlock-check.html [ Timeout ]
 crbug.com/626703 [ Win10.20h2 ] external/wpt/fledge/tentative/direct-from-seller-signals.https.window.html?6-10 [ Skip Timeout ]
 crbug.com/626703 external/wpt/css/css-contain/content-visibility/content-visibility-095.html [ Failure ]
@@ -7131,3 +7130,11 @@
 
 # Gardener 2023-11-16
 crbug.com/1502794 external/wpt/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html [ Failure Pass ]
+
+# Gerdener 2023-11-17
+crbug.com/1503108 [ Mac ] external/wpt/credential-management/fedcm-revoke.sub.https.html [ Timeout ]
+crbug.com/1503051 [ Mac ] virtual/keepalive-in-browser-migration/external/wpt/fetch/metadata/generated/audioworklet.https.sub.html [ Pass Failure ]
+crbug.com/1503067 [ Linux ] virtual/plz-dedicated-worker-disabled/external/wpt/fetch/private-network-access/window-open.tentative.https.window.html [ Pass Failure ]
+crbug.com/1503067 [ Linux ] virtual/pna-iframes-warning/external/wpt/fetch/private-network-access/window-open.tentative.https.window.html [ Pass Failure ]
+crbug.com/1503067 [ Linux ] virtual/pna-iframes-enabled/external/wpt/fetch/private-network-access/iframe.tentative.https.window.html [ Pass Failure ]
+crbug.com/1503086 [ Linux ] external/wpt/html/semantics/invokers/invoketarget-on-audio-behavior.tentative.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-auto-003-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-auto-003-expected.txt
new file mode 100644
index 0000000..fe2e1ea
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-position-auto-003-expected.txt
@@ -0,0 +1,15 @@
+This is a testharness.js-based test.
+[PASS] .target 1
+[PASS] .target 2
+[PASS] .target 3
+[PASS] .target 4
+[FAIL] .target 5
+  assert_equals: \n<div class="target" id="target5" data-offset-x="450" data-offset-y="200"></div>\noffsetLeft expected 450 but got 250
+[FAIL] .target 6
+  assert_equals: \n<div class="target" id="target6" data-offset-x="200" data-offset-y="450"></div>\noffsetTop expected 450 but got 250
+[FAIL] .target 7
+  assert_equals: \n<div class="target" id="target7" data-offset-x="250" data-offset-y="450"></div>\noffsetTop expected 450 but got 250
+[FAIL] .target 8
+  assert_equals: \n<div class="target" id="target8" data-offset-x="450" data-offset-y="250"></div>\noffsetLeft expected 450 but got 250
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/router-rules.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/router-rules.js
new file mode 100644
index 0000000..7b6a714
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/router-rules.js
@@ -0,0 +1,45 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+const routerRules = {
+  'condition-url-pattern-source-network': [
+    {
+      condition: {
+        urlPattern: new URLPattern({
+          pathname: '/**/direct.txt'
+        })
+      },
+      source: 'network'
+    },
+
+  ],
+  'condition-request-source-network': [
+    {
+      condition: {
+        requestMode: 'no-cors'
+      },
+      source: 'network'
+    }
+  ],
+  'condition-or-source-network': [
+    {
+      condition: {
+        or: [
+          {
+            or: [
+              {
+                urlPattern: '/**/or-test/direct1.*??*'
+              }
+            ],
+          },
+          {
+            urlPattern: '/**/or-test/direct2.*??*'
+          }
+        ]
+      },
+      source: 'network'
+    }
+  ],
+};
+
+export {routerRules};
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js
index 0cde920..1e02dbb 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/resources/static-router-sw.js
@@ -1,33 +1,13 @@
 'use strict';
 
+import {routerRules} from './router-rules.js';
+
 var requests = [];
 
-self.addEventListener('install', e => {
-  e.registerRouter([
-    {condition: {requestMode: 'no-cors'}, source: 'network'}, {
-      condition: {urlPattern: '/**/*.txt??*'},
-      // Note: "??*" is for allowing arbitrary query strings.
-      // Upon my experiment, the URLPattern needs two '?'s for specifying
-      // a coming string as a query.
-      source: 'network'
-    },
-    {
-      condition:
-          {urlPattern: '/**/simple-test-for-condition-main-resource.html'},
-      source: 'network'
-    },
-    {
-      condition: {
-        or: [
-          {
-            or: [{urlPattern: '/**/or-test/direct1.*??*'}],
-          },
-          {urlPattern: '/**/or-test/direct2.*??*'}
-        ]
-      },
-      source: 'network'
-    }
-  ]);
+self.addEventListener('install', async e => {
+  const params = new URLSearchParams(location.search);
+  const key = params.get('key');
+  await e.addRoutes(routerRules[key]);
   self.skipWaiting();
 });
 
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html
index 5a55783a..dda75f2 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-main-resource.https.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
-<title>Static Router: simply skip fetch handler for main resource if pattern matches</title>
+<title>
+  Static Router: simply skip fetch handler for main resource if pattern matches
+</title>
 <script src="/common/get-host-info.sub.js"></script>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
@@ -8,17 +10,19 @@
 <body>
 <script>
 const SCRIPT = 'resources/static-router-sw.js';
+const ROUTER_RULE_KEY = 'condition-url-pattern-source-network'
 const SCOPE = 'resources/';
-const REGISTERED_ROUTE_HTML =
-  'resources/simple-test-for-condition-main-resource.html';
-const NON_REGISTERED_ROUTE_HTML = 'resources/simple.html';
+const REGISTERED_ROUTE = 'resources/direct.txt';
+const NON_REGISTERED_ROUTE = 'resources/simple.html';
 const host_info = get_host_info();
 const path = new URL(".", window.location).pathname;
 
 // Register a service worker, then create an iframe at url.
-function iframeTest(url, callback, name) {
+function iframeTest(url, ruleKey, callback, name) {
   return promise_test(async t => {
-    const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+    const swURL = `${SCRIPT}?key=${ruleKey}`;
+    const reg = await service_worker_unregister_and_register(
+      t, swURL, SCOPE, {type: 'module'});
     add_completion_callback(() => reg.unregister());
     const worker = reg.installing;
     await wait_for_state(t, worker, 'activated');
@@ -37,20 +41,20 @@
     });
 }
 
-iframeTest(REGISTERED_ROUTE_HTML, async (t, iwin, worker) => {
+iframeTest(REGISTERED_ROUTE, ROUTER_RULE_KEY, async (t, iwin, worker) => {
   const fetched_urls = await get_fetched_urls(worker);
   const {requests} = fetched_urls.data;
   assert_equals(requests.length, 0);
-  assert_equals(iwin.document.body.innerText, "Here's a simple html file.");
+  assert_equals(iwin.document.body.innerText, "Network\n");
 }, 'Main resource load matched with the condition');
 
-iframeTest(NON_REGISTERED_ROUTE_HTML, async (t, iwin, worker) => {
+iframeTest(NON_REGISTERED_ROUTE, ROUTER_RULE_KEY, async (t, iwin, worker) => {
   const fetched_urls = await get_fetched_urls(worker);
   const {requests} = fetched_urls.data;
   assert_equals(requests.length, 1);
   assert_equals(
     requests[0].url,
-    `${host_info['HTTPS_ORIGIN']}${path}${NON_REGISTERED_ROUTE_HTML}`);
+    `${host_info['HTTPS_ORIGIN']}${path}${NON_REGISTERED_ROUTE}`);
   assert_equals(requests[0].mode, 'navigate');
 }, 'Main resource load not matched with the condition');
 
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html
index 42e4da8..aa58105 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/tentative/static-router/static-router-subresource.https.html
@@ -7,6 +7,9 @@
 <body>
 <script>
 const SCRIPT = 'resources/static-router-sw.js';
+const ROUTER_RULE_KEY_URL_PATTERN = 'condition-url-pattern-source-network'
+const ROUTER_RULE_KEY_REQUEST = 'condition-request-source-network'
+const ROUTER_RULE_KEY_OR = 'condition-or-source-network'
 const SCOPE = 'resources/';
 const HTML_FILE = 'resources/simple.html';
 const TXT_FILE = 'resources/direct.txt';
@@ -20,9 +23,11 @@
 ];
 
 // Register a service worker, then create an iframe at url.
-function iframeTest(url, callback, name) {
+function iframeTest(url, ruleKey, callback, name) {
   return promise_test(async t => {
-    const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+    const swURL = `${SCRIPT}?key=${ruleKey}`;
+    const reg = await service_worker_unregister_and_register(
+      t, swURL, SCOPE, {type: 'module'});
     add_completion_callback(() => reg.unregister());
     await wait_for_state(t, reg.installing, 'activated');
     const iframe = await with_iframe(url);
@@ -40,37 +45,37 @@
   return result;
 }
 
-iframeTest(HTML_FILE, async (t, iwin) => {
+iframeTest(HTML_FILE, ROUTER_RULE_KEY_URL_PATTERN, async (t, iwin) => {
   const rnd = randomString();
   const response = await iwin.fetch('?nonce=' + rnd);
   assert_equals(await response.text(), rnd);
 }, 'Subresource load not matched with URLPattern condition');
 
-iframeTest(TXT_FILE, async (t, iwin) => {
+iframeTest(TXT_FILE, ROUTER_RULE_KEY_URL_PATTERN, async (t, iwin) => {
   const rnd = randomString();
   const response = await iwin.fetch('?nonce=' + rnd);
   assert_equals(await response.text(), "Network\n");
 }, 'Subresource load matched with URLPattern condition');
 
-iframeTest(CSV_FILE, async (t, iwin) => {
+iframeTest(CSV_FILE, ROUTER_RULE_KEY_REQUEST, async (t, iwin) => {
   const rnd = randomString();
   const response = await iwin.fetch('?nonce=' + rnd, { mode: 'no-cors' });
   assert_equals(await response.text(), "matched,with,non-url,conditions\n");
 }, 'Subresource load matched with RequestMode condition');
 
-iframeTest(OR_TEST_FILES[0], async (t, iwin) => {
+iframeTest(OR_TEST_FILES[0], ROUTER_RULE_KEY_OR, async (t, iwin) => {
   const rnd = randomString();
   const response = await iwin.fetch('?nonce=' + rnd);
   assert_equals(await response.text(), "Network\n");
 }, 'Subresource load matched with the nested `or` condition');
 
-iframeTest(OR_TEST_FILES[1], async (t, iwin) => {
+iframeTest(OR_TEST_FILES[1], ROUTER_RULE_KEY_OR, async (t, iwin) => {
   const rnd = randomString();
   const response = await iwin.fetch('?nonce=' + rnd);
   assert_equals(await response.text(), "Network\n");
 }, 'Subresource load matched with the next `or` condition');
 
-iframeTest(OR_TEST_FILES[2], async (t, iwin) => {
+iframeTest(OR_TEST_FILES[2], ROUTER_RULE_KEY_OR, async (t, iwin) => {
   const rnd = randomString();
   const response = await iwin.fetch('?nonce=' + rnd);
   assert_equals(await response.text(), rnd);
diff --git a/third_party/blink/web_tests/http/tests/navigation/resources/submit-to-fragment.pl b/third_party/blink/web_tests/http/tests/navigation/resources/submit-to-fragment.pl
index 0f5dea2..eed3ce0a 100755
--- a/third_party/blink/web_tests/http/tests/navigation/resources/submit-to-fragment.pl
+++ b/third_party/blink/web_tests/http/tests/navigation/resources/submit-to-fragment.pl
@@ -14,11 +14,12 @@
   <input type="submit" value="Submit">
 </form>
 <div id="result"></div>
+<script src="/resources/prevent-bfcache.js"></script>
 <script>
-onunload = function() {
-  // no page cache
-}
 onload = function() {
+  setTimeout(async function () {
+    await preventBFCache();
+  }, 0);
   alert("stage: " + sessionStorage.stage);
   switch (sessionStorage.stage++) {
   case 1:
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index 61c4f0b..84e8cf1 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit 61c4f0ba1165b4df1b69408e47a5c3b096f5af50
+Subproject commit 84e8cf17ab35bc49df8fcad65854f24f5472418b
diff --git a/third_party/cros-components/src b/third_party/cros-components/src
index 8d31f9f..d04234e 160000
--- a/third_party/cros-components/src
+++ b/third_party/cros-components/src
@@ -1 +1 @@
-Subproject commit 8d31f9f910806d357fa2031e152c4b415a0c7fd1
+Subproject commit d04234ed17e4e8d2d95dc360b3bc3b0e6b0e3d6d
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 696d434..c03a3a5 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 696d434318c314d5bbdbfd9fcd5102707bd48bba
+Subproject commit c03a3a50f1ade6eee1ef4e77bfd355469a10b38f
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index c5a9286..d13f23b 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit c5a9286c173131ad93ef12abe79202cd59dae86b
+Subproject commit d13f23b6b05b66be191294a0be6832b1d5f66a3a
diff --git a/third_party/perfetto b/third_party/perfetto
index 6ba75cc..8670673 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 6ba75cc5d93c39d4f86e2777709a460b30ce06fc
+Subproject commit 867067368dec61b75f63cf6798c25c49670d1e8c
diff --git a/third_party/skia b/third_party/skia
index bcd22e8..8e9e168 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit bcd22e8f95bc68ef8c160cf49d335befc3b63366
+Subproject commit 8e9e168418a01ef4d7e4b2db81971a8c8d59dae9
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index 2b2dc1a5..3e1e1ca 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -36,7 +36,7 @@
 # Reverting problematic clang rolls is safe, though.
 # This is the output of `git describe` and is usable as a commit-ish.
 CLANG_REVISION = 'llvmorg-18-init-9505-g10664813'
-CLANG_SUB_REVISION = 1
+CLANG_SUB_REVISION = 2
 
 PACKAGE_VERSION = '%s-%s' % (CLANG_REVISION, CLANG_SUB_REVISION)
 RELEASE_VERSION = '18'
diff --git a/tools/code_coverage/OWNERS b/tools/code_coverage/OWNERS
index 66675b8b..2853ac81 100644
--- a/tools/code_coverage/OWNERS
+++ b/tools/code_coverage/OWNERS
@@ -2,3 +2,4 @@
 pasthana@google.com
 benreich@chromium.org
 tiborg@chromium.org
+per-file *fuzz*.py=file://testing/libfuzzer/OWNERS
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index ae4e600..e0ee0cd 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -14417,6 +14417,12 @@
   <int value="109" label="chrome_recorder.replay-recording"/>
   <int value="110" label="chrome_recorder.toggle-code-view"/>
   <int value="111" label="chrome_recorder.copy-recording-or-step"/>
+  <int value="112" label="changes.revert"/>
+  <int value="113" label="changes.copy"/>
+  <int value="114" label="elements.new-style-rule"/>
+  <int value="115" label="elements.refresh-event-listeners"/>
+  <int value="116" label="coverage.clear"/>
+  <int value="117" label="coverage.export"/>
 </enum>
 
 <enum name="DevToolsLanguage">
@@ -36443,6 +36449,7 @@
   <int value="-614223913"
       label="ClickToCallContextMenuForSelectedText:enabled"/>
   <int value="-613596048" label="new-canvas-2d-api"/>
+  <int value="-613072171" label="ElementCapture:enabled"/>
   <int value="-612860466" label="Projector:disabled"/>
   <int value="-612633819" label="NotificationScrollBar:disabled"/>
   <int value="-612480090" label="FasterLocationReload:enabled"/>
@@ -36849,6 +36856,7 @@
   <int value="-418041538" label="HelpAppHomePageAppArticles:disabled"/>
   <int value="-417633330" label="MessagesForAndroidInfrastructure:enabled"/>
   <int value="-416660617" label="EnforceTLS13Downgrade:disabled"/>
+  <int value="-415762341" label="ElementCapture:disabled"/>
   <int value="-415186532" label="AndroidSiteSettingsUIRefresh:enabled"/>
   <int value="-414900505" label="ScrollCapture:enabled"/>
   <int value="-414477658" label="DefaultBucketUsesRelaxedDurability:disabled"/>
@@ -54635,6 +54643,7 @@
   <int value="6" label="kInvalidSource"/>
   <int value="7" label="kInvalidCondition"/>
   <int value="8" label="kExceedMaxConditionDepth"/>
+  <int value="9" label="kExceedMaxRouterSize"/>
 </enum>
 
 <enum name="ServiceWorkerRouterSourceType">
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index 87821d4..27782c3 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -245,14 +245,6 @@
   <variant name=".FromSettingsPage" summary="Opt-in from settings page"/>
 </variants>
 
-<variants name="AutofillDataAvailability">
-  <variant name=".WithBothServerAndLocalData"
-      summary="both server and local autofill data"/>
-  <variant name=".WithNoData" summary="no autofill data"/>
-  <variant name=".WithOnlyLocalData" summary="only local autofill data"/>
-  <variant name=".WithOnlyServerData" summary="only server autofill data"/>
-</variants>
-
 <variants name="AutofillFieldFillingStats">
   <variant name=".Accepted"
       summary="The field was autofilled without subsequent corrections."/>
@@ -419,6 +411,14 @@
   <variant name=".VirtualCardEnrollment" summary="virtual card enrollment"/>
 </variants>
 
+<variants name="CreditCardDataAvailability">
+  <variant name=".WithBothServerAndLocalData"
+      summary="both server and local autofill data"/>
+  <variant name=".WithNoData" summary="no autofill data"/>
+  <variant name=".WithOnlyLocalData" summary="only local autofill data"/>
+  <variant name=".WithOnlyServerData" summary="only server autofill data"/>
+</variants>
+
 <variants name="IbanTypeToBeSaved">
   <variant name=".Local" summary="Local IBAN save"/>
 </variants>
@@ -1934,14 +1934,14 @@
   </summary>
 </histogram>
 
-<histogram name="Autofill.FormEvents.Address{AutofillDataAvailability}"
-    enum="AutofillFormEvent" expires_after="2024-12-12">
+<histogram name="Autofill.FormEvents.Address" enum="AutofillFormEvent"
+    expires_after="2024-12-12">
   <owner>battre@chromium.org</owner>
   <owner>koerber@google.com</owner>
   <owner>chrome-autofill-alerts@google.com</owner>
   <summary>
     Autofill form events for address forms. These are recorded when the user
-    interacts with a form requesting an address. {AutofillDataAvailability}
+    interacts with a form requesting an address.
 
     Important caveat about submission metrics: - Submission using autofill data
     is determined by simply evaluating if there was a fill operation in this
@@ -1953,9 +1953,6 @@
     filled with server, we will only emit &quot;Submitted with server suggestion
     filled (once)&quot;.
   </summary>
-  <token key="AutofillDataAvailability" variants="AutofillDataAvailability">
-    <variant name=""/>
-  </token>
 </histogram>
 
 <histogram name="Autofill.FormEvents.CreditCard.WithOffer"
@@ -1969,13 +1966,13 @@
   </summary>
 </histogram>
 
-<histogram name="Autofill.FormEvents.CreditCard{AutofillDataAvailability}"
+<histogram name="Autofill.FormEvents.CreditCard{CreditCardDataAvailability}"
     enum="AutofillFormEvent" expires_after="2024-07-01">
   <owner>jsaul@google.com</owner>
   <owner>battre@chromium.org</owner>
   <summary>
     Autofill form events for credit card forms. These are recorded when the user
-    interacts with a form requesting a credit card. {AutofillDataAvailability}
+    interacts with a form requesting a credit card. {CreditCardDataAvailability}
 
     Important caveat about submission metrics: - Submission using autofill data
     is determined by simply evaluating if there was a fill operation in this
@@ -1987,7 +1984,7 @@
     filled with server, we will only emit &quot;Submitted with server suggestion
     filled (once)&quot;.
   </summary>
-  <token key="AutofillDataAvailability" variants="AutofillDataAvailability">
+  <token key="CreditCardDataAvailability" variants="CreditCardDataAvailability">
     <variant name=""/>
   </token>
 </histogram>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index ac7c81a..7c85c0f7 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -107,12 +107,12 @@
 </histogram>
 
 <histogram name="Compositing.ColorGamut" enum="ColorGamut"
-    expires_after="2022-02-06">
-  <owner>cblume@chromium.org</owner>
-  <owner>khushalsagar@chromium.org</owner>
+    expires_after="2024-04-16">
+  <owner>vasilyt@chromium.org</owner>
+  <owner>graphics-dev@chromium.org</owner>
   <summary>
     Every time a frame is presented, record the generalized color gamut of that
-    frame.
+    frame. Note: this metric was expired from 2022-02-06 to 2023-11-17.
   </summary>
 </histogram>
 
@@ -513,22 +513,23 @@
 </histogram>
 
 <histogram name="Compositing.Display.VizDependencyResolvedToGpuStartedDrawUs"
-    units="microseconds" expires_after="2022-12-18">
+    units="microseconds" expires_after="2024-04-16">
   <owner>vasilyt@chromium.org</owner>
-  <owner>backer@chromium.org</owner>
+  <owner>graphics-dev@chromium.org</owner>
   <summary>
     This is logged once per frame, if the output surface provides timing
     information. It measures delta between the time when display compositor draw
     task's dependencies have been solved and the time when the Gpu Thread
     started processing it. Only reported for platforms supporting high
-    resolution clocks.
+    resolution clocks. Note: this metric was expired from 2022-12-18 to
+    2023-11-17.
   </summary>
 </histogram>
 
 <histogram name="Compositing.Display.VizScheduledDrawToDependencyResolvedUs"
-    units="microseconds" expires_after="2023-08-27">
+    units="microseconds" expires_after="2024-04-16">
   <owner>vasilyt@chromium.org</owner>
-  <owner>backer@chromium.org</owner>
+  <owner>graphics-dev@chromium.org</owner>
   <summary>
     This is logged once per frame if the output surface provides timing
     information. It measures delta between the time when display compositor
@@ -536,19 +537,21 @@
     the time when the task's dependencies have been resolved and the task is
     ready for the Gpu Thread to start processing it. Recorded when swap
     completes. Only reported for platforms supporting high resolution clocks.
+    Note: this metric was expired from 2023-08-27 to 2023-11-17.
   </summary>
 </histogram>
 
 <histogram name="Compositing.Display.VizScheduledDrawToGpuStartedDrawUs"
-    units="microseconds" expires_after="2022-12-18">
+    units="microseconds" expires_after="2024-04-16">
   <owner>vasilyt@chromium.org</owner>
-  <owner>backer@chromium.org</owner>
+  <owner>graphics-dev@chromium.org</owner>
   <summary>
     This is logged once per frame if the output surface provides timing
     information. It measures delta between the time when display compositor
     scheduled first draw task for the current frame on Viz Compositor thread and
     the time when Gpu Thread started processing it. Recorded when swap
     completes. Only reported for platforms supporting high resolution clocks.
+    Note: this metric was expired from 2022-12-18 to 2023-11-17.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/custom_tabs/enums.xml b/tools/metrics/histograms/metadata/custom_tabs/enums.xml
index 3772c601..c0ecbc1c 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/enums.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/enums.xml
@@ -253,13 +253,6 @@
   <int value="3" label="Fallback for Status Bar and content"/>
 </enum>
 
-<enum name="TrustedWebActivityPermissionChanged">
-  <int value="0" label="Null to False"/>
-  <int value="1" label="Null to True"/>
-  <int value="2" label="True to False"/>
-  <int value="3" label="False to True"/>
-</enum>
-
 <enum name="VisibleTab">
   <int value="0" label="Custom Tab"/>
   <int value="1" label="Chrome Tab"/>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
index 03a1cd2..e6152ae 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
@@ -821,26 +821,6 @@
   </summary>
 </histogram>
 
-<histogram name="TrustedWebActivity.LocationPermissionChanged"
-    enum="TrustedWebActivityPermissionChanged" expires_after="2023-11-12">
-  <owner>eirage@chromium.org</owner>
-  <owner>peconn@chromium.org</owner>
-  <summary>
-    When a Trusted Web Activity client app's location permission is changed,
-    record the previous state and new stase.
-  </summary>
-</histogram>
-
-<histogram name="TrustedWebActivity.LocationPermissionRequestIsGranted"
-    enum="Boolean" expires_after="2023-11-19">
-  <owner>eirage@chromium.org</owner>
-  <owner>peconn@chromium.org</owner>
-  <summary>
-    Records the boolean result (granted or not) from requesting a Trusted Web
-    Activity client app's location permission.
-  </summary>
-</histogram>
-
 <histogram name="TrustedWebActivity.Notification.PermissionRequestResult"
     enum="ContentSetting" expires_after="2024-11-14">
   <owner>mvanouwerkerk@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index 9b55dc0..6494bda 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -478,11 +478,6 @@
   <affected-histogram name="Autofill.CardUploadEnabled"/>
   <affected-histogram name="Autofill.CreditCard.IsEnabled.PageLoad"/>
   <affected-histogram
-      name="Autofill.FormEvents.Address.WithBothServerAndLocalData"/>
-  <affected-histogram name="Autofill.FormEvents.Address.WithNoData"/>
-  <affected-histogram name="Autofill.FormEvents.Address.WithOnlyLocalData"/>
-  <affected-histogram name="Autofill.FormEvents.Address.WithOnlyServerData"/>
-  <affected-histogram
       name="Autofill.FormEvents.CreditCard.WithBothServerAndLocalData"/>
   <affected-histogram name="Autofill.FormEvents.CreditCard.WithNoData"/>
   <affected-histogram name="Autofill.FormEvents.CreditCard.WithOnlyLocalData"/>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index 5173d36..7476c0f 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -230,7 +230,7 @@
 </histogram>
 
 <histogram name="PasswordBubble.AddUsernameBubble.UsernameAdded"
-    enum="BooleanSuccess" expires_after="M130">
+    enum="BooleanYesNo" expires_after="M130">
   <owner>sygiet@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/service/histograms.xml b/tools/metrics/histograms/metadata/service/histograms.xml
index 5b4f75d..c9733898 100644
--- a/tools/metrics/histograms/metadata/service/histograms.xml
+++ b/tools/metrics/histograms/metadata/service/histograms.xml
@@ -172,66 +172,6 @@
   </summary>
 </histogram>
 
-<histogram name="ServiceWorker.BackgroundFetchAbortEvent.Time" units="ms"
-    expires_after="2022-06-30">
-  <owner>nator@chromium.org</owner>
-  <owner>peter@chromium.org</owner>
-  <owner>rayankans@chromium.org</owner>
-  <summary>
-    The time taken between dispatching a BackgroundFetchAbortEvent to a Service
-    Worker and receiving a message that it finished handling the event. Includes
-    the time for the waitUntil() promise to settle.
-  </summary>
-</histogram>
-
-<histogram name="ServiceWorker.BackgroundFetchClickEvent.Time" units="ms"
-    expires_after="2022-06-30">
-  <owner>nator@chromium.org</owner>
-  <owner>peter@chromium.org</owner>
-  <owner>rayankans@chromium.org</owner>
-  <summary>
-    The time taken between dispatching a BackgroundFetchClickEvent to a Service
-    Worker and receiving a message that it finished handling the event. Includes
-    the time for the waitUntil() promise to settle.
-  </summary>
-</histogram>
-
-<histogram name="ServiceWorker.BackgroundFetchFailEvent.Time" units="ms"
-    expires_after="2022-06-30">
-  <owner>nator@chromium.org</owner>
-  <owner>peter@chromium.org</owner>
-  <owner>rayankans@chromium.org</owner>
-  <summary>
-    The time taken between dispatching a BackgroundFetchFailEvent to a Service
-    Worker and receiving a message that it finished handling the event. Includes
-    the time for the waitUntil() promise to settle.
-  </summary>
-</histogram>
-
-<histogram name="ServiceWorker.BackgroundFetchSuccessEvent.Time" units="ms"
-    expires_after="2022-06-30">
-  <owner>nator@chromium.org</owner>
-  <owner>peter@chromium.org</owner>
-  <owner>rayankans@chromium.org</owner>
-  <summary>
-    The time taken between dispatching a BackgroundFetchSuccessEvent to a
-    Service Worker and receiving a message that it finished handling the event.
-    Includes the time for the waitUntil() promise to settle. This event is sent
-    when the background fetch succeeds.
-  </summary>
-</histogram>
-
-<histogram name="ServiceWorker.BackgroundSyncEvent.Time" units="ms"
-    expires_after="2022-06-30">
-  <owner>nator@chromium.org</owner>
-  <owner>rayankans@chromium.org</owner>
-  <summary>
-    The time taken between dispatching a SyncEvent to a Service Worker and
-    receiving a message that it finished handling the event. Includes the time
-    for the waitUntil() promise to settle.
-  </summary>
-</histogram>
-
 <histogram name="ServiceWorker.CacheStorageInstalledScript.CachedMetadataSize"
     units="bytes" expires_after="2024-06-01">
   <owner>yyanagisawa@chromium.org</owner>
@@ -1236,17 +1176,6 @@
   </summary>
 </histogram>
 
-<histogram name="ServiceWorker.PushEvent.Time" units="ms"
-    expires_after="2022-10-30">
-  <owner>peter@chromium.org</owner>
-  <owner>knollr@chromium.org</owner>
-  <summary>
-    The time taken between dispatching a PushEvent to a Service Worker and
-    receiving a message that it finished handling the event. Includes the time
-    for the waitUntil() promise to settle.
-  </summary>
-</histogram>
-
 <histogram name="ServiceWorker.RegisteredStorageKeyCount" units="origins"
     expires_after="2024-04-28">
   <owner>yyanagisawa@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/web_rtc/histograms.xml b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
index 8b16e3e..b000c386 100644
--- a/tools/metrics/histograms/metadata/web_rtc/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
@@ -1221,6 +1221,18 @@
   </summary>
 </histogram>
 
+<histogram name="WebRTC.PeerConnection.DtlsFingerprintLegacySha1"
+    units="boolean" expires_after="2024-07-01">
+  <owner>hta@chromium.org</owner>
+  <owner>webrtc-dev@chromium.org</owner>
+  <owner>phancke@microsoft.com</owner>
+  <summary>
+    Measures the use of the weak SHA-1 hash algorithm in the X509 certificate
+    presented by the peer. Recorded during the first DTLS connection
+    establishment.
+  </summary>
+</histogram>
+
 <histogram name="WebRTC.PeerConnection.DtlsHandshakeError"
     enum="DtlsHandshakeError" expires_after="M81">
   <owner>zhihuang@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index af1a6904..5cf966f 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "5f971ea2e4d79d08e59b5dcfcaa3d2415661f0c5",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/6ba75cc5d93c39d4f86e2777709a460b30ce06fc/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/ddba2c4202fcb6534241e44061edf49d3b134e07/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/metadata_model_unittest.js b/ui/file_manager/file_manager/foreground/js/metadata/metadata_model_unittest.js
deleted file mode 100644
index 8e1eb5d0..0000000
--- a/ui/file_manager/file_manager/foreground/js/metadata/metadata_model_unittest.js
+++ /dev/null
@@ -1,334 +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.
-
-import {assertEquals, assertThrows} from 'chrome://webui-test/chromeos/chai_assert.js';
-
-import {reportPromise} from '../../../common/js/test_error_reporting.js';
-
-import {MetadataModel} from './metadata_model.js';
-import {MetadataProvider} from './metadata_provider.js';
-
-/** @final */
-class TestMetadataProvider extends MetadataProvider {
-  constructor() {
-    super(['property', 'propertyA', 'propertyB']);
-    this.requestCount = 0;
-  }
-
-  /** @override */
-  // @ts-ignore: error TS7006: Parameter 'requests' implicitly has an 'any'
-  // type.
-  get(requests) {
-    this.requestCount++;
-    // @ts-ignore: error TS7006: Parameter 'request' implicitly has an 'any'
-    // type.
-    return Promise.resolve(requests.map(request => {
-      const entry = request.entry;
-      const names = request.names;
-      const result = {};
-      for (let i = 0; i < names.length; i++) {
-        // @ts-ignore: error TS7053: Element implicitly has an 'any' type
-        // because expression of type 'any' can't be used to index type '{}'.
-        result[names[i]] = entry.toURL() + ':' + names[i];
-      }
-      return result;
-    }));
-  }
-}
-
-/** @final */
-class TestEmptyMetadataProvider extends MetadataProvider {
-  constructor() {
-    super(['property']);
-  }
-
-  /** @override */
-  // @ts-ignore: error TS7006: Parameter 'requests' implicitly has an 'any'
-  // type.
-  get(requests) {
-    return Promise.resolve(requests.map(() => {
-      return {};
-    }));
-  }
-}
-
-/** @final */
-class ManualTestMetadataProvider extends MetadataProvider {
-  constructor() {
-    super(['propertyA', 'propertyB', 'propertyC']);
-    // @ts-ignore: error TS7008: Member 'callback' implicitly has an 'any[]'
-    // type.
-    this.callback = [];
-  }
-
-  /** @override */
-  // @ts-ignore: error TS7006: Parameter 'requests' implicitly has an 'any'
-  // type.
-  get(requests) {
-    return new Promise(fulfill => {
-      this.callback.push(fulfill);
-    });
-  }
-}
-
-/** @type {!Entry} */
-const entryA = /** @type {!Entry} */ ({
-  toURL: function() {
-    return 'filesystem://A';
-  },
-});
-
-/** @type {!Entry} */
-const entryB = /** @type {!Entry} */ ({
-  toURL: function() {
-    return 'filesystem://B';
-  },
-});
-
-/**
- * Returns a property of a Metadata result object.
- * @param {Object} result Metadata result
- * @param {string} property Property name to return.
- * @return {string}
- */
-function getProperty(result, property) {
-  if (!result) {
-    throw new Error('Fail: Metadata result is undefined');
-  }
-  // @ts-ignore: error TS7053: Element implicitly has an 'any' type because
-  // expression of type 'string' can't be used to index type 'Object'.
-  return result[property];
-}
-
-/** @param {()=>void} callback */
-export function testMetadataModelBasic(callback) {
-  let provider = new TestMetadataProvider();
-  const model = new MetadataModel(provider);
-
-  reportPromise(
-      model.get([entryA, entryB], ['property']).then(results => {
-        provider = /** @type {!TestMetadataProvider} */ (model.getProvider());
-        assertEquals(1, provider.requestCount);
-        assertEquals(
-            // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-            // undefined' is not assignable to parameter of type 'Object'.
-            'filesystem://A:property', getProperty(results[0], 'property'));
-        assertEquals(
-            // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-            // undefined' is not assignable to parameter of type 'Object'.
-            'filesystem://B:property', getProperty(results[1], 'property'));
-      }),
-      callback);
-}
-
-/** @param {()=>void} callback */
-export function testMetadataModelRequestForCachedProperty(callback) {
-  let provider = new TestMetadataProvider();
-  const model = new MetadataModel(provider);
-
-  reportPromise(
-      model.get([entryA, entryB], ['property'])
-          .then(() => {
-            // All the results should be cached here.
-            return model.get([entryA, entryB], ['property']);
-          })
-          .then(results => {
-            provider =
-                /** @type {!TestMetadataProvider} */ (model.getProvider());
-            assertEquals(1, provider.requestCount);
-            assertEquals(
-                // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-                // undefined' is not assignable to parameter of type 'Object'.
-                'filesystem://A:property', getProperty(results[0], 'property'));
-            assertEquals(
-                // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-                // undefined' is not assignable to parameter of type 'Object'.
-                'filesystem://B:property', getProperty(results[1], 'property'));
-          }),
-      callback);
-}
-
-export function testMetadataModelRequestForCachedAndNonCachedProperty(
-    // @ts-ignore: error TS7006: Parameter 'callback' implicitly has an 'any'
-    // type.
-    callback) {
-  let provider = new TestMetadataProvider();
-  const model = new MetadataModel(provider);
-
-  reportPromise(
-      model.get([entryA, entryB], ['propertyA'])
-          .then(() => {
-            provider =
-                /** @type {!TestMetadataProvider} */ (model.getProvider());
-            assertEquals(1, provider.requestCount);
-            // propertyB has not been cached here.
-            return model.get([entryA, entryB], ['propertyA', 'propertyB']);
-          })
-          .then(results => {
-            provider =
-                /** @type {!TestMetadataProvider} */ (model.getProvider());
-            assertEquals(2, provider.requestCount);
-            assertEquals(
-                'filesystem://A:propertyA',
-                // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-                // undefined' is not assignable to parameter of type 'Object'.
-                getProperty(results[0], 'propertyA'));
-            assertEquals(
-                'filesystem://A:propertyB',
-                // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-                // undefined' is not assignable to parameter of type 'Object'.
-                getProperty(results[0], 'propertyB'));
-            assertEquals(
-                'filesystem://B:propertyA',
-                // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-                // undefined' is not assignable to parameter of type 'Object'.
-                getProperty(results[1], 'propertyA'));
-            assertEquals(
-                'filesystem://B:propertyB',
-                // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-                // undefined' is not assignable to parameter of type 'Object'.
-                getProperty(results[1], 'propertyB'));
-          }),
-      callback);
-}
-
-/** @param {()=>void} callback */
-export function testMetadataModelRequestForCachedAndNonCachedEntry(callback) {
-  let provider = new TestMetadataProvider();
-  const model = new MetadataModel(provider);
-
-  reportPromise(
-      model.get([entryA], ['property'])
-          .then(() => {
-            provider =
-                /** @type {!TestMetadataProvider} */ (model.getProvider());
-            assertEquals(1, provider.requestCount);
-            // entryB has not been cached here.
-            return model.get([entryA, entryB], ['property']);
-          })
-          .then(results => {
-            provider =
-                /** @type {!TestMetadataProvider} */ (model.getProvider());
-            assertEquals(2, provider.requestCount);
-            assertEquals(
-                // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-                // undefined' is not assignable to parameter of type 'Object'.
-                'filesystem://A:property', getProperty(results[0], 'property'));
-            assertEquals(
-                // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-                // undefined' is not assignable to parameter of type 'Object'.
-                'filesystem://B:property', getProperty(results[1], 'property'));
-          }),
-      callback);
-}
-
-export function testMetadataModelRequestBeforeCompletingPreviousRequest(
-    // @ts-ignore: error TS7006: Parameter 'callback' implicitly has an 'any'
-    // type.
-    callback) {
-  let provider = new TestMetadataProvider();
-  const model = new MetadataModel(provider);
-
-  model.get([entryA], ['property']);
-  provider = /** @type {!TestMetadataProvider} */ (model.getProvider());
-  assertEquals(1, provider.requestCount);
-
-  // The result of first call has not been fetched yet.
-  reportPromise(
-      model.get([entryA], ['property']).then(results => {
-        provider = /** @type {!TestMetadataProvider} */ (model.getProvider());
-        assertEquals(1, provider.requestCount);
-        assertEquals(
-            // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-            // undefined' is not assignable to parameter of type 'Object'.
-            'filesystem://A:property', getProperty(results[0], 'property'));
-      }),
-      callback);
-}
-
-/** @param {()=>void} callback */
-export function testMetadataModelNotUpdateCachedResultAfterRequest(callback) {
-  let provider = new ManualTestMetadataProvider();
-  const model = new MetadataModel(provider);
-
-  const promise = model.get([entryA], ['propertyA']);
-  provider = /** @type {!ManualTestMetadataProvider} */ (model.getProvider());
-  provider.callback[0]([{propertyA: 'valueA1'}]);
-
-  reportPromise(
-      promise
-          .then(() => {
-            // 'propertyA' is cached here.
-            const promise1 = model.get([entryA], ['propertyA', 'propertyB']);
-            const promise2 = model.get([entryA], ['propertyC']);
-            // Returns propertyC.
-            provider = /** @type {!ManualTestMetadataProvider} */ (
-                model.getProvider());
-            provider.callback[2]([{propertyA: 'valueA2', propertyC: 'valueC'}]);
-            provider.callback[1]([{propertyB: 'valueB'}]);
-            return Promise.all([promise1, promise2]);
-          })
-          .then(results => {
-            // The result should be cached value at the time when get was
-            // called.
-            // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-            // undefined' is not assignable to parameter of type 'Object'.
-            assertEquals('valueA1', getProperty(results[0][0], 'propertyA'));
-            // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-            // undefined' is not assignable to parameter of type 'Object'.
-            assertEquals('valueB', getProperty(results[0][0], 'propertyB'));
-            // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-            // undefined' is not assignable to parameter of type 'Object'.
-            assertEquals('valueC', getProperty(results[1][0], 'propertyC'));
-          }),
-      callback);
-}
-
-/** @param {()=>void} callback */
-export function testMetadataModelGetCache(callback) {
-  let provider = new TestMetadataProvider();
-  const model = new MetadataModel(provider);
-
-  const promise = model.get([entryA], ['property']);
-  const cache = model.getCache([entryA], ['property']);
-  // @ts-ignore: error TS2345: Argument of type 'MetadataItem | undefined' is
-  // not assignable to parameter of type 'Object'.
-  assertEquals(null, getProperty(cache[0], 'property'));
-
-  reportPromise(
-      promise.then(() => {
-        const cache = model.getCache([entryA], ['property']);
-        provider = /** @type {!TestMetadataProvider} */ (model.getProvider());
-        assertEquals(1, provider.requestCount);
-        assertEquals(
-            // @ts-ignore: error TS2345: Argument of type 'MetadataItem |
-            // undefined' is not assignable to parameter of type 'Object'.
-            'filesystem://A:property', getProperty(cache[0], 'property'));
-      }),
-      callback);
-}
-
-export function testMetadataModelUnknownProperty() {
-  const provider = new TestMetadataProvider();
-  const model = new MetadataModel(provider);
-
-  assertThrows(() => {
-    model.get([entryA], ['unknown']);
-  });
-}
-
-/** @param {()=>void} callback */
-export function testMetadataModelEmptyResult(callback) {
-  const provider = new TestEmptyMetadataProvider();
-  const model = new MetadataModel(provider);
-
-  // getImpl returns empty result.
-  reportPromise(
-      model.get([entryA], ['property']).then(results => {
-        // @ts-ignore: error TS2345: Argument of type 'MetadataItem | undefined'
-        // is not assignable to parameter of type 'Object'.
-        assertEquals(undefined, getProperty(results[0], 'property'));
-      }),
-      callback);
-}
diff --git a/ui/file_manager/file_manager/foreground/js/metadata/metadata_model_unittest.ts b/ui/file_manager/file_manager/foreground/js/metadata/metadata_model_unittest.ts
new file mode 100644
index 0000000..6ce2ed8b
--- /dev/null
+++ b/ui/file_manager/file_manager/foreground/js/metadata/metadata_model_unittest.ts
@@ -0,0 +1,270 @@
+// 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.
+
+import {assertEquals, assertThrows} from 'chrome://webui-test/chromeos/chai_assert.js';
+
+import {reportPromise} from '../../../common/js/test_error_reporting.js';
+import {FilesAppEntry} from '../../../externs/files_app_entry_interfaces.js';
+
+import {MetadataItem, MetadataKey} from './metadata_item.js';
+import {MetadataModel} from './metadata_model.js';
+import {MetadataProvider} from './metadata_provider.js';
+import {MetadataRequest} from './metadata_request.js';
+
+class TestMetadataProvider extends MetadataProvider {
+  requestCount = 0;
+
+  constructor() {
+    super(['thumbnailUrl', 'mediaAlbum', 'mediaArtist']);
+  }
+
+  override get(requests: MetadataRequest[]) {
+    this.requestCount++;
+    return Promise.resolve(requests.map(request => {
+      const entry = request.entry;
+      const result: MetadataItem = {};
+      for (const name of request.names) {
+        // Only string metadata properties are valid in this metadata provider.
+        (result[name] as string) = entry.toURL() + ':' + name;
+      }
+      return result;
+    }));
+  }
+}
+
+class TestEmptyMetadataProvider extends MetadataProvider {
+  constructor() {
+    super(['thumbnailUrl']);
+  }
+
+  override get(requests: MetadataRequest[]) {
+    return Promise.resolve(requests.map(() => {
+      return {};
+    }));
+  }
+}
+
+class ManualTestMetadataProvider extends MetadataProvider {
+  callback: Array<(value: MetadataItem[]) => void> = [];
+
+  constructor() {
+    super(['mediaAlbum', 'mediaArtist', 'alternateUrl']);
+  }
+
+  override get(_requests: MetadataRequest[]) {
+    return new Promise<MetadataItem[]>(fulfill => {
+      this.callback.push(fulfill);
+    });
+  }
+}
+
+class TestFilesAppEntry extends FilesAppEntry {
+  constructor(private url_: string) {
+    super();
+  }
+
+  // This function implements an existing function from FileSystemEntry, so we
+  // can't change the name.
+  // eslint-disable-next-line @typescript-eslint/naming-convention
+  override toURL() {
+    return this.url_;
+  }
+}
+
+const entryA = new TestFilesAppEntry('filesystem://A');
+const entryB = new TestFilesAppEntry('filesystem://B');
+
+/**
+ * Returns a property of a Metadata result object.
+ */
+function getProperty<K extends MetadataKey>(
+    result: MetadataItem|undefined, property: K): MetadataItem[K] {
+  if (!result) {
+    throw new Error('Fail: Metadata result is undefined');
+  }
+  return result[property];
+}
+
+export function testMetadataModelBasic(callback: () => void) {
+  const provider = new TestMetadataProvider();
+  const model = new MetadataModel(provider);
+
+  reportPromise(
+      model.get([entryA, entryB], ['thumbnailUrl']).then(results => {
+        assertEquals(1, provider.requestCount);
+        assertEquals(
+            'filesystem://A:thumbnailUrl',
+            getProperty(results[0], 'thumbnailUrl'));
+        assertEquals(
+            'filesystem://B:thumbnailUrl',
+            getProperty(results[1], 'thumbnailUrl'));
+      }),
+      callback);
+}
+
+export function testMetadataModelRequestForCachedProperty(
+    callback: () => void) {
+  const provider = new TestMetadataProvider();
+  const model = new MetadataModel(provider);
+
+  reportPromise(
+      model.get([entryA, entryB], ['thumbnailUrl'])
+          .then(() => {
+            // All the results should be cached here.
+            return model.get([entryA, entryB], ['thumbnailUrl']);
+          })
+          .then(results => {
+            assertEquals(1, provider.requestCount);
+            assertEquals(
+                'filesystem://A:thumbnailUrl',
+                getProperty(results[0], 'thumbnailUrl'));
+            assertEquals(
+                'filesystem://B:thumbnailUrl',
+                getProperty(results[1], 'thumbnailUrl'));
+          }),
+      callback);
+}
+
+export function testMetadataModelRequestForCachedAndNonCachedProperty(
+    callback: () => void) {
+  const provider = new TestMetadataProvider();
+  const model = new MetadataModel(provider);
+
+  reportPromise(
+      model.get([entryA, entryB], ['mediaAlbum'])
+          .then(() => {
+            assertEquals(1, provider.requestCount);
+            // mediaArtist has not been cached here.
+            return model.get([entryA, entryB], ['mediaAlbum', 'mediaArtist']);
+          })
+          .then(results => {
+            assertEquals(2, provider.requestCount);
+            assertEquals(
+                'filesystem://A:mediaAlbum',
+                getProperty(results[0], 'mediaAlbum'));
+            assertEquals(
+                'filesystem://A:mediaArtist',
+                getProperty(results[0], 'mediaArtist'));
+            assertEquals(
+                'filesystem://B:mediaAlbum',
+                getProperty(results[1], 'mediaAlbum'));
+            assertEquals(
+                'filesystem://B:mediaArtist',
+                getProperty(results[1], 'mediaArtist'));
+          }),
+      callback);
+}
+
+export function testMetadataModelRequestForCachedAndNonCachedEntry(
+    callback: () => void) {
+  const provider = new TestMetadataProvider();
+  const model = new MetadataModel(provider);
+
+  reportPromise(
+      model.get([entryA], ['thumbnailUrl'])
+          .then(() => {
+            assertEquals(1, provider.requestCount);
+            // entryB has not been cached here.
+            return model.get([entryA, entryB], ['thumbnailUrl']);
+          })
+          .then(results => {
+            assertEquals(2, provider.requestCount);
+            assertEquals(
+                'filesystem://A:thumbnailUrl',
+                getProperty(results[0], 'thumbnailUrl'));
+            assertEquals(
+                'filesystem://B:thumbnailUrl',
+                getProperty(results[1], 'thumbnailUrl'));
+          }),
+      callback);
+}
+
+export function testMetadataModelRequestBeforeCompletingPreviousRequest(
+    callback: () => void) {
+  const provider = new TestMetadataProvider();
+  const model = new MetadataModel(provider);
+
+  model.get([entryA], ['thumbnailUrl']);
+  assertEquals(1, provider.requestCount);
+
+  // The result of first call has not been fetched yet.
+  reportPromise(
+      model.get([entryA], ['thumbnailUrl']).then(results => {
+        assertEquals(1, provider.requestCount);
+        assertEquals(
+            'filesystem://A:thumbnailUrl',
+            getProperty(results[0], 'thumbnailUrl'));
+      }),
+      callback);
+}
+
+export function testMetadataModelNotUpdateCachedResultAfterRequest(
+    callback: () => void) {
+  const provider = new ManualTestMetadataProvider();
+  const model = new MetadataModel(provider);
+
+  const promise = model.get([entryA], ['mediaAlbum']);
+  provider.callback[0]!([{mediaAlbum: 'album1'}]);
+
+  reportPromise(
+      promise
+          .then(() => {
+            // 'mediaAlbum' is cached here.
+            const promise1 = model.get([entryA], ['mediaAlbum', 'mediaArtist']);
+            const promise2 = model.get([entryA], ['alternateUrl']);
+            // Returns alternateUrl.
+            provider.callback[2]!
+                ([{mediaAlbum: 'album2', alternateUrl: 'urlC'}]);
+            provider.callback[1]!([{mediaArtist: 'artistB'}]);
+            return Promise.all([promise1, promise2]);
+          })
+          .then(results => {
+            // The result should be cached value at the time when get was
+            // called.
+            assertEquals('album1', getProperty(results[0][0], 'mediaAlbum'));
+            assertEquals('artistB', getProperty(results[0][0], 'mediaArtist'));
+            assertEquals('urlC', getProperty(results[1][0], 'alternateUrl'));
+          }),
+      callback);
+}
+
+export function testMetadataModelGetCache(callback: () => void) {
+  const provider = new TestMetadataProvider();
+  const model = new MetadataModel(provider);
+
+  const promise = model.get([entryA], ['thumbnailUrl']);
+  const cache = model.getCache([entryA], ['thumbnailUrl']);
+  assertEquals(null, getProperty(cache[0], 'thumbnailUrl'));
+
+  reportPromise(
+      promise.then(() => {
+        const cache = model.getCache([entryA], ['thumbnailUrl']);
+        assertEquals(1, provider.requestCount);
+        assertEquals(
+            'filesystem://A:thumbnailUrl',
+            getProperty(cache[0], 'thumbnailUrl'));
+      }),
+      callback);
+}
+
+export function testMetadataModelUnknownProperty() {
+  const provider = new TestMetadataProvider();
+  const model = new MetadataModel(provider);
+
+  assertThrows(() => {
+    model.get([entryA], ['unknown']);
+  });
+}
+
+export function testMetadataModelEmptyResult(callback: () => void) {
+  const provider = new TestEmptyMetadataProvider();
+  const model = new MetadataModel(provider);
+
+  // getImpl returns empty result.
+  reportPromise(
+      model.get([entryA], ['thumbnailUrl']).then(results => {
+        assertEquals(undefined, getProperty(results[0], 'thumbnailUrl'));
+      }),
+      callback);
+}
diff --git a/ui/file_manager/file_names.gni b/ui/file_manager/file_names.gni
index a6033054..5cde7afc 100644
--- a/ui/file_manager/file_names.gni
+++ b/ui/file_manager/file_names.gni
@@ -543,6 +543,7 @@
   "file_manager/foreground/js/metadata/content_metadata_provider_unittest.ts",
   "file_manager/foreground/js/metadata/exif_parser_unittest.ts",
   "file_manager/foreground/js/metadata/file_system_metadata_provider_unittest.ts",
+  "file_manager/foreground/js/metadata/metadata_model_unittest.ts",
 ]
 
 ts_generated_templates = []
@@ -615,7 +616,6 @@
   # Foreground:
   "file_manager/foreground/js/metadata/metadata_cache_item_unittest.js",
   "file_manager/foreground/js/metadata/thumbnail_model_unittest.js",
-  "file_manager/foreground/js/metadata/metadata_model_unittest.js",
   "file_manager/foreground/js/metadata/external_metadata_provider_unittest.js",
   "file_manager/foreground/js/metadata/metadata_cache_set_unittest.js",
   "file_manager/foreground/js/metadata/multi_metadata_provider_unittest.js",
diff --git a/v8 b/v8
index 0820381..c53da02 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 0820381ccfe67b31f2f65548450d5e1a15a6cffe
+Subproject commit c53da0241d47fd6684a2865df5102929ba87eee8