diff --git a/DEPS b/DEPS
index c095436..95f01ca 100644
--- a/DEPS
+++ b/DEPS
@@ -309,7 +309,7 @@
   # 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': 'b7d581997f2b1cebb8f8361f84c2eab8162b2836',
+  'skia_revision': '5fbacd5d68a5e722eb516ed91301d894f57ac00c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -317,7 +317,7 @@
   # 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': '81e3ecff1c859823723d9abf3441626076400e69',
+  'angle_revision': 'a75659eb40cc887619bb1e4d4efb91030891ced7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -360,7 +360,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '0f98994ef6f71596890d98444f70b448c9bf0bdc',
+  'freetype_revision': '667aad581a0419d2180087ee1c7e7d7dfac8462f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # 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': 'cc2be1ac1c1ed9a68d7d151025dfa1d0c8a378b4',
+  'chromium_variations_revision': 'bbcb5613de7c77e7bc70b77b0849b238ce472f29',
   # 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': 'a5651fc48b0a1fedb93779da685535c11100bd31',
+  'devtools_frontend_revision': 'bac5f4964ab90c18db43d5ab2bcbc427f7227ad4',
   # 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.
@@ -424,7 +424,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '46b9ded3a891f6c7597aa9ea6647628541c61fff',
+  'dawn_revision': '235d4e59cb461ae67751d591d10315e427b0a934',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -496,7 +496,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling beto-core
   # and whatever else without interference from each other.
-  'betocore_revision': 'e0af43baab8771dd02d56532eab1a4f98d42ac9a',
+  'betocore_revision': '96e6e374c28a01caf35c7fb620eba078aeb655bb',
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
@@ -824,7 +824,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '037ef38c3ed595b087c9a5340ebba24feb6c4956',
+    '0f952cb0548617cdb61a432963ef1b3b57beac10',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1218,13 +1218,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '7a9b709a6c649b3900f4eed0e855ef1541dbd642',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'b0cfbe504be43b05745de4ddff41ffba7feea706',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '733813f828425537fcbc51db93716e4e7033343c',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '7622fc040bfb73ebf8f196260dbe70d725310f33',
     'condition': 'checkout_src_internal',
   },
 
@@ -1997,7 +1997,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'MhPI29ImVYg2YEchNIf1E46OasMmN7zeOaG2WkZbcEAC',
+        'version': 'ocm3JJw_FQIRU8k4YXYNTnR-puxdCPRnVBQljwn2yVwC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4031,7 +4031,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '26cadba9564d99a7fe8e13433f80c37e6e1d3ed7',
+        'ffad5e3cdc70181d9913574cd90ede7b9be515da',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
@@ -4110,7 +4110,7 @@
   },
 
   'src/third_party/ml': {
-      'url': Var('chrome_git') + '/chrome/third_party/ml.git' + '@' + '982c7b896a65d1c09fa1720bf29c104b108b4621',
+      'url': Var('chrome_git') + '/chrome/third_party/ml.git' + '@' + '4b4be935e1100edfb078cfff61b39063a09e90a3',
       'condition': 'checkout_third_party_ml',
   },
 
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn
index 8a616eb..65ab64a 100644
--- a/android_webview/BUILD.gn
+++ b/android_webview/BUILD.gn
@@ -576,9 +576,9 @@
     "java/src/org/chromium/android_webview/AwGeolocationPermissions.java",
     "java/src/org/chromium/android_webview/AwHistogramRecorder.java",
     "java/src/org/chromium/android_webview/AwHttpAuthHandler.java",
-    "java/src/org/chromium/android_webview/AwIntegrityApiStatusConfig.java",
     "java/src/org/chromium/android_webview/AwKeyboardShortcuts.java",
     "java/src/org/chromium/android_webview/AwLayoutSizer.java",
+    "java/src/org/chromium/android_webview/AwMediaIntegrityApiStatusConfig.java",
     "java/src/org/chromium/android_webview/AwNetworkChangeNotifierRegistrationPolicy.java",
     "java/src/org/chromium/android_webview/AwPacProcessor.java",
     "java/src/org/chromium/android_webview/AwPdfExporter.java",
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index e535187..89534a1 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -1346,4 +1346,9 @@
   NOTREACHED_NORETURN();
 }
 
+network::mojom::IpProtectionProxyBypassPolicy
+AwContentBrowserClient::GetIpProtectionProxyBypassPolicy() {
+  return network::mojom::IpProtectionProxyBypassPolicy::kNone;
+}
+
 }  // namespace android_webview
diff --git a/android_webview/browser/aw_content_browser_client.h b/android_webview/browser/aw_content_browser_client.h
index 5c416249..6dd7da1 100644
--- a/android_webview/browser/aw_content_browser_client.h
+++ b/android_webview/browser/aw_content_browser_client.h
@@ -282,6 +282,8 @@
   bool ShouldUseOsWebTriggerAttributionReporting(
       content::RenderFrameHost* rfh) override;
   blink::mojom::OriginTrialsSettingsPtr GetOriginTrialsSettings() override;
+  network::mojom::IpProtectionProxyBypassPolicy
+  GetIpProtectionProxyBypassPolicy() override;
 
   AwFeatureListCreator* aw_feature_list_creator() {
     return aw_feature_list_creator_;
diff --git a/android_webview/java/src/org/chromium/android_webview/AwIntegrityApiStatusConfig.java b/android_webview/java/src/org/chromium/android_webview/AwMediaIntegrityApiStatusConfig.java
similarity index 87%
rename from android_webview/java/src/org/chromium/android_webview/AwIntegrityApiStatusConfig.java
rename to android_webview/java/src/org/chromium/android_webview/AwMediaIntegrityApiStatusConfig.java
index 6934ee0..651d940 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwIntegrityApiStatusConfig.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwMediaIntegrityApiStatusConfig.java
@@ -19,10 +19,14 @@
  * levels for origin sites through defaults and override rules.
  */
 @Lifetime.WebView
-public class AwIntegrityApiStatusConfig {
+public class AwMediaIntegrityApiStatusConfig {
     @Target(ElementType.TYPE_USE)
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ApiStatus.DISABLED, ApiStatus.ENABLED_WITHOUT_APP_IDENTITY, ApiStatus.ENABLED})
+    @IntDef({
+        ApiStatus.DISABLED,
+        ApiStatus.ENABLED_WITHOUT_APP_IDENTITY,
+        ApiStatus.ENABLED
+    })
     public @interface ApiStatus {
         int DISABLED = 0;
         int ENABLED_WITHOUT_APP_IDENTITY = 1;
@@ -32,7 +36,7 @@
     private @ApiStatus int mDefaultStatus;
     private Map<String, @ApiStatus Integer> mOverrideRulesToPermission;
 
-    public AwIntegrityApiStatusConfig() {
+    public AwMediaIntegrityApiStatusConfig() {
         mDefaultStatus = ApiStatus.ENABLED;
         mOverrideRulesToPermission = Collections.emptyMap();
     }
diff --git a/android_webview/java/src/org/chromium/android_webview/AwSettings.java b/android_webview/java/src/org/chromium/android_webview/AwSettings.java
index 5d3c8fe..070c4fe 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwSettings.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwSettings.java
@@ -21,7 +21,7 @@
 import org.jni_zero.JNINamespace;
 import org.jni_zero.NativeMethods;
 
-import org.chromium.android_webview.AwIntegrityApiStatusConfig.ApiStatus;
+import org.chromium.android_webview.AwMediaIntegrityApiStatusConfig.ApiStatus;
 import org.chromium.android_webview.client_hints.AwUserAgentMetadata;
 import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.android_webview.common.Lifetime;
@@ -187,7 +187,7 @@
     private boolean mSupportZoom = true;
     private boolean mBuiltInZoomControls;
     private boolean mDisplayZoomControls = true;
-    private final AwIntegrityApiStatusConfig mIntegrityApiStatusConfig;
+    private final AwMediaIntegrityApiStatusConfig mIntegrityApiStatusConfig;
 
     // Cache default user agent string obtained through JNI, since it will not change during the
     // process lifetime. This saves a JNI call when creating new AwSettings objects after the first
@@ -350,7 +350,7 @@
             } else {
                 mRequestedWithHeaderAllowedOriginRules = Collections.emptySet();
             }
-            mIntegrityApiStatusConfig = new AwIntegrityApiStatusConfig();
+            mIntegrityApiStatusConfig = new AwMediaIntegrityApiStatusConfig();
         }
         // Defer initializing the native side until a native WebContents instance is set.
     }
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index f27ebcd..c0297c2c2 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -303,6 +303,9 @@
                 AutofillFeatures.AUTOFILL_ENABLE_EMAIL_HEURISTIC_ONLY_ADDRESS_FORMS,
                 "When enabled, Autofill supports forms consisting of only email fields."),
         Flag.baseFeature(
+                AutofillFeatures.AUTOFILL_TEXT_AREA_CHANGE_EVENTS,
+                "When enabled, autofill responds to textarea change events."),
+        Flag.baseFeature(
                 FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_VIRTUAL_CARD_FEATURE,
                 "When enabled, merchant bound virtual cards will be offered in the keyboard "
                         + "accessory."),
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwIntegrityApiStatusConfigTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwMediaIntegrityApiStatusConfigTest.java
similarity index 71%
rename from android_webview/javatests/src/org/chromium/android_webview/test/AwIntegrityApiStatusConfigTest.java
rename to android_webview/javatests/src/org/chromium/android_webview/test/AwMediaIntegrityApiStatusConfigTest.java
index 3e78fc3..224ac3ce 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwIntegrityApiStatusConfigTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwMediaIntegrityApiStatusConfigTest.java
@@ -10,18 +10,18 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.android_webview.AwIntegrityApiStatusConfig;
-import org.chromium.android_webview.AwIntegrityApiStatusConfig.ApiStatus;
+import org.chromium.android_webview.AwMediaIntegrityApiStatusConfig;
+import org.chromium.android_webview.AwMediaIntegrityApiStatusConfig.ApiStatus;
 
 import java.util.Map;
 
-/** {@link org.chromium.android_webview.AwIntegrityApiStatusConfig} tests. */
+/** {@link org.chromium.android_webview.AwMediaIntegrityApiStatusConfig} tests. */
 @RunWith(AwJUnit4ClassRunner.class)
-public class AwIntegrityApiStatusConfigTest {
+public class AwMediaIntegrityApiStatusConfigTest {
     @Test
     @SmallTest
     public void testGetApiStatus_returnsEmptyConfig_whenNotSet() throws Throwable {
-        AwIntegrityApiStatusConfig config = new AwIntegrityApiStatusConfig();
+        AwMediaIntegrityApiStatusConfig config = new AwMediaIntegrityApiStatusConfig();
 
         Assert.assertEquals(ApiStatus.ENABLED, config.getDefaultStatus());
         Assert.assertTrue(config.getOverrideRules().isEmpty());
@@ -30,7 +30,7 @@
     @Test
     @SmallTest
     public void testGetApiStatus_returnsConfig_whenSetWithRules() throws Throwable {
-        AwIntegrityApiStatusConfig config = new AwIntegrityApiStatusConfig();
+        AwMediaIntegrityApiStatusConfig config = new AwMediaIntegrityApiStatusConfig();
         Map<String, @ApiStatus Integer> overrideRules =
                 Map.of("http://*.webview.com", ApiStatus.ENABLED_WITHOUT_APP_IDENTITY);
         @ApiStatus int defaultPermission = ApiStatus.DISABLED;
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebSettingsBoundaryInterface.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebSettingsBoundaryInterface.java
index f41fcb08d..8555ea2 100644
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebSettingsBoundaryInterface.java
+++ b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/WebSettingsBoundaryInterface.java
@@ -9,8 +9,10 @@
 // app-facing classes should have a boundary-interface that the WebView glue layer can build
 // against.
 
+import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.Map;
 import java.util.Set;
 
@@ -79,4 +81,21 @@
 
     @AttributionBehavior
     int getAttributionBehavior();
+
+    @Target(ElementType.TYPE_USE)
+    @Retention(RetentionPolicy.SOURCE)
+    @interface WebViewMediaIntegrityApiStatus {
+        int DISABLED = 0;
+        int ENABLED_WITHOUT_APP_IDENTITY = 1;
+        int ENABLED = 2;
+    }
+
+    void setWebViewMediaIntegrityApiStatus(
+            @WebViewMediaIntegrityApiStatus int defaultPermission,
+            Map<String, @WebViewMediaIntegrityApiStatus Integer> permissionConfig);
+
+    @WebViewMediaIntegrityApiStatus
+    int getWebViewMediaIntegrityApiDefaultStatus();
+
+    Map<String, @WebViewMediaIntegrityApiStatus Integer> getWebViewMediaIntegrityApiOverrideRules();
 }
diff --git a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java
index 20200e1..d2e99f3 100644
--- a/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java
+++ b/android_webview/support_library/boundary_interfaces/src/org/chromium/support_lib_boundary/util/Features.java
@@ -266,4 +266,9 @@
     // WebSettingsCompat.setAttributionBehavior
     // WebSettingsCompat.getAttributionBehavior
     public static final String ATTRIBUTION_BEHAVIOR = "ATTRIBUTION_BEHAVIOR";
+
+    // WebSettingsCompat.setWebViewMediaIntegrityApiStatus
+    // WebSettingsCompat.getWebViewMediaIntegrityApiDefaultStatus
+    // WebSettingsCompat.getWebViewMediaIntegrityApiOverrideRules
+    public static final String WEBVIEW_MEDIA_INTEGRITY_API_STATUS = "WEBVIEW_INTEGRITY_API_STATUS";
 }
diff --git a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebSettingsAdapter.java b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebSettingsAdapter.java
index 841ba8b..47d2898 100644
--- a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebSettingsAdapter.java
+++ b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebSettingsAdapter.java
@@ -9,12 +9,14 @@
 import android.webkit.WebSettings;
 
 import org.chromium.android_webview.AwDarkMode;
+import org.chromium.android_webview.AwMediaIntegrityApiStatusConfig.ApiStatus;
 import org.chromium.android_webview.AwSettings;
 import org.chromium.base.Log;
 import org.chromium.base.TraceEvent;
 import org.chromium.support_lib_boundary.WebSettingsBoundaryInterface;
 import org.chromium.support_lib_glue.SupportLibWebViewChromiumFactory.ApiCall;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
@@ -309,4 +311,76 @@
             return AttributionBehavior.APP_SOURCE_AND_WEB_TRIGGER;
         }
     }
+
+    @Override
+    public void setWebViewMediaIntegrityApiStatus(
+            @WebViewMediaIntegrityApiStatus int defaultStatus,
+            Map<String, @WebViewMediaIntegrityApiStatus Integer> permissionConfig) {
+        try (TraceEvent event =
+                TraceEvent.scoped(
+                    "WebView.APICall.AndroidX.SET_WEBVIEW_MEDIA_INTEGRITY_API_STATUS")) {
+            recordApiCall(ApiCall.SET_WEBVIEW_MEDIA_INTEGRITY_API_STATUS);
+
+            Map<String, @ApiStatus Integer> permissionToApiStatus = new HashMap<>();
+            for (Map.Entry<String, @WebViewMediaIntegrityApiStatus Integer> entry :
+                    permissionConfig.entrySet()) {
+                permissionToApiStatus.put(entry.getKey(), convertToApiStatus(entry.getValue()));
+            }
+            @ApiStatus int defaultApiStatus = convertToApiStatus(defaultStatus);
+            mAwSettings.setWebViewIntegrityApiStatus(defaultApiStatus, permissionToApiStatus);
+        }
+    }
+
+    @Override
+    public @WebViewMediaIntegrityApiStatus int getWebViewMediaIntegrityApiDefaultStatus() {
+        try (TraceEvent event =
+                TraceEvent.scoped(
+                    "WebView.APICall.AndroidX.GET_WEBVIEW_MEDIA_INTEGRITY_API_DEFAULT_STATUS")) {
+            recordApiCall(ApiCall.GET_WEBVIEW_MEDIA_INTEGRITY_API_DEFAULT_STATUS);
+            return convertFromApiStatus(mAwSettings.getWebViewIntegrityApiDefaultStatus());
+        }
+    }
+
+    @Override
+    public Map<String, @WebViewMediaIntegrityApiStatus Integer>
+            getWebViewMediaIntegrityApiOverrideRules() {
+        try (TraceEvent event =
+                TraceEvent.scoped(
+                        "WebView.APICall.AndroidX.GET_WEBVIEW_MEDIA_INTEGRITY_API_OVERRIDE_RULES")) {
+            recordApiCall(ApiCall.GET_WEBVIEW_MEDIA_INTEGRITY_API_OVERRIDE_RULES);
+            Map<String, @ApiStatus Integer> overrideRules = new HashMap<>();
+            for (Map.Entry<String, @ApiStatus Integer> entry :
+                    mAwSettings.getWebViewIntegrityApiOverrideRules().entrySet()) {
+                overrideRules.put(entry.getKey(), convertFromApiStatus(entry.getValue()));
+            }
+            return overrideRules;
+        }
+    }
+
+    private @ApiStatus Integer convertToApiStatus(@WebViewMediaIntegrityApiStatus int status) {
+        switch (status) {
+            case WebViewMediaIntegrityApiStatus.DISABLED:
+                return ApiStatus.DISABLED;
+            case WebViewMediaIntegrityApiStatus.ENABLED_WITHOUT_APP_IDENTITY:
+                return ApiStatus.ENABLED_WITHOUT_APP_IDENTITY;
+            case WebViewMediaIntegrityApiStatus.ENABLED:
+                return ApiStatus.ENABLED;
+        }
+        throw new IllegalArgumentException(
+            "Invalid WebView Media Integrity API status: " + status);
+    }
+
+    private @WebViewMediaIntegrityApiStatus Integer convertFromApiStatus(@ApiStatus int status) {
+        switch (status) {
+            case ApiStatus.DISABLED:
+                return WebViewMediaIntegrityApiStatus.DISABLED;
+            case ApiStatus.ENABLED_WITHOUT_APP_IDENTITY:
+                return WebViewMediaIntegrityApiStatus.ENABLED_WITHOUT_APP_IDENTITY;
+            case ApiStatus.ENABLED:
+                return WebViewMediaIntegrityApiStatus.ENABLED;
+        }
+        // unreached
+        throw new IllegalArgumentException(
+            "Invalid WebView Media Integrity API status: " + status);
+    }
 }
diff --git a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
index 7921b410..6899e1f 100644
--- a/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
+++ b/android_webview/support_library/java/src/org/chromium/support_lib_glue/SupportLibWebViewChromiumFactory.java
@@ -93,6 +93,7 @@
                     Features.USER_AGENT_METADATA,
                     Features.MULTI_PROFILE,
                     Features.ATTRIBUTION_BEHAVIOR,
+                    Features.WEBVIEW_MEDIA_INTEGRITY_API_STATUS + Features.DEV_SUFFIX,
                     // Add new features above. New features must include `+ Features.DEV_SUFFIX`
                     // when they're initially added (this can be removed in a future CL). The final
                     // feature should have a trailing comma for cleaner diffs.
@@ -196,6 +197,9 @@
             ApiCall.GET_WEBVIEW_PROFILE,
             ApiCall.SET_ATTRIBUTION_BEHAVIOR,
             ApiCall.GET_ATTRIBUTION_BEHAVIOR,
+            ApiCall.GET_WEBVIEW_MEDIA_INTEGRITY_API_DEFAULT_STATUS,
+            ApiCall.GET_WEBVIEW_MEDIA_INTEGRITY_API_OVERRIDE_RULES,
+            ApiCall.SET_WEBVIEW_MEDIA_INTEGRITY_API_STATUS,
             // Add new constants above. The final constant should have a trailing comma for cleaner
             // diffs.
             ApiCall.COUNT, // Added to suppress WrongConstant in #recordApiCall
@@ -304,8 +308,11 @@
         int GET_WEBVIEW_PROFILE = 94;
         int SET_ATTRIBUTION_BEHAVIOR = 95;
         int GET_ATTRIBUTION_BEHAVIOR = 96;
+        int GET_WEBVIEW_MEDIA_INTEGRITY_API_DEFAULT_STATUS = 97;
+        int GET_WEBVIEW_MEDIA_INTEGRITY_API_OVERRIDE_RULES = 98;
+        int SET_WEBVIEW_MEDIA_INTEGRITY_API_STATUS = 99;
         // Remember to update AndroidXWebkitApiCall in enums.xml when adding new values here
-        int COUNT = 97;
+        int COUNT = 100;
     }
 
     public static void recordApiCall(@ApiCall int apiCall) {
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index e5eeaf14..fed8c30 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -325,10 +325,10 @@
     "../javatests/src/org/chromium/android_webview/test/AwDisplayCutoutTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwFileChooserTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwImeTest.java",
-    "../javatests/src/org/chromium/android_webview/test/AwIntegrityApiStatusConfigTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwJavaBridgeTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwKeyboardShortcutsTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwLegacyQuirksTest.java",
+    "../javatests/src/org/chromium/android_webview/test/AwMediaIntegrityApiStatusConfigTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwMetricsIntegrationTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwMetricsLogUploaderTest.java",
     "../javatests/src/org/chromium/android_webview/test/AwNetworkConfigurationTest.java",
diff --git a/android_webview/tools/system_webview_shell/layout_tests/src/org/chromium/webview_shell/test/WebViewLayoutTest.java b/android_webview/tools/system_webview_shell/layout_tests/src/org/chromium/webview_shell/test/WebViewLayoutTest.java
index 71d1d50..fa15c123 100644
--- a/android_webview/tools/system_webview_shell/layout_tests/src/org/chromium/webview_shell/test/WebViewLayoutTest.java
+++ b/android_webview/tools/system_webview_shell/layout_tests/src/org/chromium/webview_shell/test/WebViewLayoutTest.java
@@ -393,6 +393,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "crbug.com/1502191")
     public void testGeolocationCallbacks() throws Exception {
         runWebViewLayoutTest(
                 "blink-apis/geolocation/geolocation-permission-callbacks.html",
@@ -423,6 +424,7 @@
 
     @Test
     @MediumTest
+    @DisabledTest(message = "crbug.com/1502191")
     public void testBatteryApi() throws Exception {
         runWebViewLayoutTest(
                 "blink-apis/battery-status/battery-callback.html",
diff --git a/ash/app_list/views/app_list_bubble_apps_page.cc b/ash/app_list/views/app_list_bubble_apps_page.cc
index 9db75cd1..7a35618 100644
--- a/ash/app_list/views/app_list_bubble_apps_page.cc
+++ b/ash/app_list/views/app_list_bubble_apps_page.cc
@@ -40,6 +40,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/aura/window.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/compositor/animation_throughput_reporter.h"
@@ -113,6 +114,8 @@
 
 // A view that runs a click callback when clicked or tapped.
 class ClickableView : public views::View {
+  METADATA_HEADER(ClickableView, views::View)
+
  public:
   explicit ClickableView(base::RepeatingClosure click_callback)
       : click_callback_(click_callback) {}
@@ -142,6 +145,9 @@
   base::RepeatingClosure click_callback_;
 };
 
+BEGIN_METADATA(ClickableView)
+END_METADATA
+
 }  // namespace
 
 AppListBubbleAppsPage::AppListBubbleAppsPage(
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc
index d510511..19901ba 100644
--- a/ash/app_list/views/app_list_item_view.cc
+++ b/ash/app_list/views/app_list_item_view.cc
@@ -54,6 +54,7 @@
 #include "ui/base/dragdrop/drag_drop_types.h"
 #include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
@@ -124,7 +125,7 @@
 
 // The duration of the animation to animate an app list item view in as a
 // promise app replacement.
-constexpr base::TimeDelta kSwapPromiseIconDuration = base::Milliseconds(1000);
+constexpr base::TimeDelta kSwapPromiseIconDuration = base::Milliseconds(100);
 
 // The amount of space between the progress ring and the promise app background.
 constexpr gfx::Insets kProgressRingMargin = gfx::Insets(-2);
@@ -250,6 +251,8 @@
 
 // Draws a dot with no shadow.
 class DotView : public views::View {
+  METADATA_HEADER(DotView, views::View)
+
  public:
   DotView()
       : color_id_(chromeos::features::IsJellyEnabled()
@@ -285,6 +288,9 @@
   const ui::ColorId color_id_;
 };
 
+BEGIN_METADATA(DotView)
+END_METADATA
+
 // Returns whether the `index` is considered on the left edge of a grid with
 // `cols` columns.
 bool IsIndexOnLeftEdge(GridIndex index, int cols) {
@@ -317,6 +323,8 @@
 
 class AppListItemView::FolderIconView : public views::View,
                                         public AppListItemListObserver {
+  METADATA_HEADER(FolderIconView, views::View)
+
  public:
   FolderIconView(AppListFolderItem* folder_item,
                  const AppListConfig* config,
@@ -536,6 +544,9 @@
   std::string dragged_item_id_;
 };
 
+BEGIN_METADATA(AppListItemView, FolderIconView, views::View)
+END_METADATA
+
 AppListItemView::AppListItemView(const AppListConfig* app_list_config,
                                  GridDelegate* grid_delegate,
                                  AppListItem* item,
@@ -743,9 +754,13 @@
     }
   }
 
-  const ui::ImageModel& image_model = ShouldUseFallbackIconImageModel()
-                                          ? fallback_icon_image_model_
-                                          : icon_image_model_;
+  const bool use_fallback_icon = ShouldUseFallbackIconImageModel();
+  const ui::ImageModel& image_model =
+      use_fallback_icon ? fallback_icon_image_model_ : icon_image_model_;
+  if (!use_fallback_icon && !fallback_icon_image_model_.IsEmpty()) {
+    fallback_icon_image_model_ = ui::ImageModel();
+  }
+
   gfx::ImageSkia image_icon;
   if (image_model.IsImage()) {
     image_icon = image_model.GetImage().AsImageSkia();
@@ -762,6 +777,10 @@
     return false;
   }
 
+  if (prefer_fallback_icon_) {
+    return true;
+  }
+
   if (!item_weak_) {
     return true;
   }
@@ -1883,6 +1902,7 @@
   forced_progress_indicator_value_ = 0.999999f;
   UpdateProgressIndicatorState();
 
+  prefer_fallback_icon_ = true;
   fallback_icon_image_model_ = fallback_image;
   UpdateIconView(/*update_item_icon=*/false);
 
@@ -1933,7 +1953,12 @@
   // Clear background set as a result of adding progress indicator.
   SetBackground(nullptr);
 
-  fallback_icon_image_model_ = ui::ImageModel();
+  prefer_fallback_icon_ = false;
+
+  if (!ShouldUseFallbackIconImageModel()) {
+    fallback_icon_image_model_ = ui::ImageModel();
+  }
+
   GetIconView()->DestroyLayer();
   UpdateIconView(/*update_item_icon=*/true);
 
diff --git a/ash/app_list/views/app_list_item_view.h b/ash/app_list/views/app_list_item_view.h
index 1dfe0ab..b93f0bf6 100644
--- a/ash/app_list/views/app_list_item_view.h
+++ b/ash/app_list/views/app_list_item_view.h
@@ -584,6 +584,11 @@
   // is being loaded.
   ui::ImageModel fallback_icon_image_model_;
 
+  // Whether fallback icon should be preferred even if the actual app icon has
+  // been loaded - set while the animation from a promise icon state is in
+  // progress.
+  bool prefer_fallback_icon_ = false;
+
   // The current item's drag state.
   DragState drag_state_ = DragState::kNone;
 
diff --git a/ash/app_list/views/app_list_toast_container_view.cc b/ash/app_list/views/app_list_toast_container_view.cc
index aed322b..cecb5e7e 100644
--- a/ash/app_list/views/app_list_toast_container_view.cc
+++ b/ash/app_list/views/app_list_toast_container_view.cc
@@ -24,6 +24,7 @@
 #include "ash/style/ash_color_id.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/compositor/layer.h"
@@ -421,4 +422,7 @@
   }
 }
 
+BEGIN_METADATA(AppListToastContainerView)
+END_METADATA
+
 }  // namespace ash
diff --git a/ash/app_list/views/app_list_toast_container_view.h b/ash/app_list/views/app_list_toast_container_view.h
index d1739a7..ea486da 100644
--- a/ash/app_list/views/app_list_toast_container_view.h
+++ b/ash/app_list/views/app_list_toast_container_view.h
@@ -11,6 +11,7 @@
 #include "ash/app_list/views/app_list_nudge_controller.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/view.h"
 
 namespace views {
@@ -33,6 +34,8 @@
 // A container view accommodating a toast view with type `ToastType`. See
 // `ToastType` for more detail.
 class AppListToastContainerView : public views::View {
+  METADATA_HEADER(AppListToastContainerView, views::View)
+
  public:
   // The visibility state of the container.
   enum class VisibilityState {
diff --git a/ash/app_list/views/app_list_toast_view.cc b/ash/app_list/views/app_list_toast_view.cc
index 8c8fb06..4af1b61 100644
--- a/ash/app_list/views/app_list_toast_view.cc
+++ b/ash/app_list/views/app_list_toast_view.cc
@@ -412,4 +412,7 @@
   title_label_->SetMaximumWidth(GetExpandedTitleLabelWidth());
 }
 
+BEGIN_METADATA(AppListToastView)
+END_METADATA
+
 }  // namespace ash
diff --git a/ash/app_list/views/app_list_toast_view.h b/ash/app_list/views/app_list_toast_view.h
index 33b80f3..e473881 100644
--- a/ash/app_list/views/app_list_toast_view.h
+++ b/ash/app_list/views/app_list_toast_view.h
@@ -37,6 +37,8 @@
 // last column and the title and subtitle will share the middle column, with the
 // title over the subtitle.
 class ASH_EXPORT AppListToastView : public views::View {
+  METADATA_HEADER(AppListToastView, views::View)
+
  public:
   class Builder {
    public:
diff --git a/ash/components/arc/compat_mode/arc_resize_lock_manager.cc b/ash/components/arc/compat_mode/arc_resize_lock_manager.cc
index 9795d863..6abe99f 100644
--- a/ash/components/arc/compat_mode/arc_resize_lock_manager.cc
+++ b/ash/components/arc/compat_mode/arc_resize_lock_manager.cc
@@ -254,8 +254,12 @@
   // kArcResizeLockTypeKey, and the new value is the same as |old| in that case.
   AppIdObserver::RunOnReady(
       window, base::BindOnce(&CompatModeButtonController::Update,
-                             compat_mode_button_controller_->GetWeakPtr(),
-                             pref_delegate_));
+                             compat_mode_button_controller_->GetWeakPtr()));
+}
+
+void ArcResizeLockManager::Shutdown() {
+  compat_mode_button_controller_->ClearPrefDelegate();
+  pref_delegate_ = nullptr;
 }
 
 void ArcResizeLockManager::OnWindowBoundsChanged(
@@ -263,7 +267,7 @@
     const gfx::Rect& old_bounds,
     const gfx::Rect& new_bounds,
     ui::PropertyChangeReason reason) {
-  compat_mode_button_controller_->Update(pref_delegate_, window);
+  compat_mode_button_controller_->Update(window);
 }
 
 void ArcResizeLockManager::OnWindowDestroying(aura::Window* window) {
@@ -272,6 +276,13 @@
     window_observations_.RemoveObservation(window);
 }
 
+void ArcResizeLockManager::SetPrefDelegate(
+    ArcResizeLockPrefDelegate* delegate) {
+  CHECK(!pref_delegate_);
+  pref_delegate_ = delegate;
+  compat_mode_button_controller_->SetPrefDelegate(delegate);
+}
+
 void ArcResizeLockManager::EnableResizeLock(aura::Window* window) {
   const bool inserted = resize_lock_enabled_windows_.insert(window).second;
   if (!inserted)
@@ -352,7 +363,7 @@
 
   // As we updated the resize lock state above, we need to update compat mode
   // button.
-  compat_mode_button_controller_->Update(pref_delegate_, window);
+  compat_mode_button_controller_->Update(window);
 
   // Even if resize lock doesn't get enabled or disabled, we need to ensure to
   // update this as resize shadow can be updated in an intermediate state when
diff --git a/ash/components/arc/compat_mode/arc_resize_lock_manager.h b/ash/components/arc/compat_mode/arc_resize_lock_manager.h
index aab7656..762f5534 100644
--- a/ash/components/arc/compat_mode/arc_resize_lock_manager.h
+++ b/ash/components/arc/compat_mode/arc_resize_lock_manager.h
@@ -41,6 +41,9 @@
   ArcResizeLockManager& operator=(const ArcResizeLockManager&) = delete;
   ~ArcResizeLockManager() override;
 
+  // KeyedService:
+  void Shutdown() override;
+
   // aura::EnvObserver:
   void OnWindowInitialized(aura::Window* new_window) override;
 
@@ -54,9 +57,10 @@
                              ui::PropertyChangeReason reason) override;
   void OnWindowDestroying(aura::Window* window) override;
 
-  void SetPrefDelegate(ArcResizeLockPrefDelegate* delegate) {
-    pref_delegate_ = delegate;
-  }
+  // Sets `pref_delegate_` to `delegate`, ensuring that it was not already set.
+  // Also, calls `compat_mode_button_controller_->SetPrefDelegate()` with
+  // `delegate`.
+  void SetPrefDelegate(ArcResizeLockPrefDelegate* delegate);
 
   static void EnsureFactoryBuilt();
 
@@ -72,6 +76,8 @@
   virtual void ShowSplashScreenDialog(aura::Window* window,
                                       bool is_fully_locked);
 
+  // TODO(b/310695064): Resolve DanglingUntriaged by separating
+  // ArcAppListPrefsDelegate from ArcAppListPrefs.
   raw_ptr<ArcResizeLockPrefDelegate, DanglingUntriaged | ExperimentalAsh>
       pref_delegate_{nullptr};
 
diff --git a/ash/components/arc/compat_mode/arc_resize_lock_manager_unittest.cc b/ash/components/arc/compat_mode/arc_resize_lock_manager_unittest.cc
index 43f8ac07..794b46526 100644
--- a/ash/components/arc/compat_mode/arc_resize_lock_manager_unittest.cc
+++ b/ash/components/arc/compat_mode/arc_resize_lock_manager_unittest.cc
@@ -66,8 +66,7 @@
   }
 
   // CompatModeButtonController:
-  void Update(ArcResizeLockPrefDelegate* pref_delegate,
-              aura::Window* window) override {
+  void Update(aura::Window* window) override {
     update_compat_mode_button_called.insert(window);
   }
 
diff --git a/ash/components/arc/compat_mode/compat_mode_button_controller.cc b/ash/components/arc/compat_mode/compat_mode_button_controller.cc
index 8201f0d..0047e87a 100644
--- a/ash/components/arc/compat_mode/compat_mode_button_controller.cc
+++ b/ash/components/arc/compat_mode/compat_mode_button_controller.cc
@@ -23,27 +23,49 @@
 
 namespace arc {
 
+CompatModeButtonController::ButtonState::ButtonState() = default;
+
+CompatModeButtonController::ButtonState::ButtonState(bool enable) {
+  this->enable = enable;
+}
+
+CompatModeButtonController::ButtonState::ButtonState(
+    bool enable,
+    const std::u16string& tooltip_text)
+    : CompatModeButtonController::ButtonState(enable) {
+  this->tooltip_text = tooltip_text;
+}
+
+CompatModeButtonController::ButtonState::ButtonState(const ButtonState& other) =
+    default;
+CompatModeButtonController::ButtonState::~ButtonState() = default;
+
 CompatModeButtonController::CompatModeButtonController() = default;
 CompatModeButtonController::~CompatModeButtonController() = default;
 
-void CompatModeButtonController::Update(
-    ArcResizeLockPrefDelegate* pref_delegate,
-    aura::Window* window) {
+void CompatModeButtonController::Update(aura::Window* window) {
   DCHECK(ash::IsArcWindow(window));
+  DCHECK(pref_delegate_);
 
-  if (ash::GameDashboardController::IsGameWindow(window)) {
-    return;
-  }
   const auto app_id = GetAppId(window);
   if (!app_id)
     return;
   auto* const frame_header = GetFrameHeader(window);
   // TODO(b/200230343): Replace it with resize lock type.
-  const auto resize_lock_state = pref_delegate->GetResizeLockState(*app_id);
+  const auto resize_lock_state = pref_delegate_->GetResizeLockState(*app_id);
   if (resize_lock_state == mojom::ArcResizeLockState::UNDEFINED ||
       resize_lock_state == mojom::ArcResizeLockState::READY) {
     return;
   }
+
+  // Update the accelerator, for all windows.
+  UpdateAshAccelerator(window);
+
+  // Don't show the `CompatModeButton` for game windows.
+  if (ash::GameDashboardController::IsGameWindow(window)) {
+    return;
+  }
+
   auto* compat_mode_button = frame_header->GetCenterButton();
   if (!compat_mode_button) {
     // The ownership is transferred implicitly with AddChildView in HeaderView,
@@ -51,7 +73,7 @@
     compat_mode_button = new CompatModeButton(
         this,
         base::BindRepeating(&CompatModeButtonController::ToggleResizeToggleMenu,
-                            GetWeakPtr(), window, pref_delegate));
+                            GetWeakPtr(), window));
     frame_header->SetCenterButton(compat_mode_button);
 
     UpdateArrowIcon(window, /*widget_visibility=*/false);
@@ -73,25 +95,12 @@
   compat_mode_button->SetText(text);
   compat_mode_button->SetAccessibleName(text);
 
-  const auto resize_lock_type = window->GetProperty(ash::kArcResizeLockTypeKey);
-  switch (resize_lock_type) {
-    case ash::ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE:
-    case ash::ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE:
-      compat_mode_button->SetEnabled(true);
-      break;
-    case ash::ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE:
-      compat_mode_button->SetEnabled(false);
-      compat_mode_button->SetTooltipText(l10n_util::GetStringUTF16(
-          IDS_ASH_ARC_APP_COMPAT_DISABLED_COMPAT_MODE_BUTTON_TOOLTIP_PHONE));
-      break;
-    case ash::ArcResizeLockType::NONE:
-      // Maximizing an app with RESIZE_ENABLED_TOGGLABLE can lead to this case.
-      // Resize lock state shouldn't be updated as the pre-maximized state
-      // needs to be restored later.
-      break;
+  if (auto button_state = GetButtonState(window)) {
+    compat_mode_button->SetEnabled(button_state->enable);
+    if (button_state->tooltip_text) {
+      compat_mode_button->SetTooltipText(button_state->tooltip_text.value());
+    }
   }
-
-  UpdateAshAccelerator(pref_delegate, window);
 }
 
 void CompatModeButtonController::OnButtonPressed() {
@@ -99,6 +108,36 @@
       resize_toggle_menu_ && resize_toggle_menu_->IsBubbleShown();
 }
 
+void CompatModeButtonController::ClearPrefDelegate() {
+  pref_delegate_ = nullptr;
+}
+
+void CompatModeButtonController::SetPrefDelegate(
+    ArcResizeLockPrefDelegate* pref_delegate) {
+  CHECK(!pref_delegate_);
+  pref_delegate_ = pref_delegate;
+}
+
+absl::optional<CompatModeButtonController::ButtonState>
+CompatModeButtonController::GetButtonState(aura::Window* window) {
+  const auto resize_lock_type = window->GetProperty(ash::kArcResizeLockTypeKey);
+  switch (resize_lock_type) {
+    case ash::ArcResizeLockType::RESIZE_DISABLED_TOGGLABLE:
+    case ash::ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE:
+      return ButtonState{/*enable=*/true};
+    case ash::ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE:
+      return ButtonState{
+          /*enable=*/false,
+          l10n_util::GetStringUTF16(
+              IDS_ASH_ARC_APP_COMPAT_DISABLED_COMPAT_MODE_BUTTON_TOOLTIP_PHONE)};
+    case ash::ArcResizeLockType::NONE:
+      // Maximizing an app with RESIZE_ENABLED_TOGGLABLE can lead to this case.
+      // Resize lock state shouldn't be updated as the pre-maximized state
+      // needs to be restored later.
+      return std::nullopt;
+  }
+}
+
 void CompatModeButtonController::UpdateArrowIcon(aura::Window* window,
                                                  bool widget_visibility) {
   auto* const frame_view = ash::NonClientFrameViewAsh::Get(window);
@@ -129,9 +168,7 @@
   return frame_view->GetHeaderView()->GetFrameHeader();
 }
 
-void CompatModeButtonController::UpdateAshAccelerator(
-    ArcResizeLockPrefDelegate* pref_delegate,
-    aura::Window* window) {
+void CompatModeButtonController::UpdateAshAccelerator(aura::Window* window) {
   auto* const frame_view = ash::NonClientFrameViewAsh::Get(window);
   // |frame_view| can be null in unittest.
   if (!frame_view)
@@ -146,7 +183,7 @@
     case ash::ArcResizeLockType::RESIZE_ENABLED_TOGGLABLE:
       frame_view->SetToggleResizeLockMenuCallback(base::BindRepeating(
           &CompatModeButtonController::ToggleResizeToggleMenu, GetWeakPtr(),
-          window, pref_delegate));
+          window));
       break;
     case ash::ArcResizeLockType::RESIZE_DISABLED_NONTOGGLABLE:
       frame_view->ClearToggleResizeLockMenuCallback();
@@ -154,11 +191,11 @@
   }
 }
 
-void CompatModeButtonController::ToggleResizeToggleMenu(
-    aura::Window* window,
-    ArcResizeLockPrefDelegate* pref_delegate) {
-  if (!window || !ash::IsArcWindow(window))
+void CompatModeButtonController::ToggleResizeToggleMenu(aura::Window* window) {
+  if (!window || !ash::IsArcWindow(window)) {
     return;
+  }
+  DCHECK(pref_delegate_);
 
   auto* frame_view = ash::NonClientFrameViewAsh::Get(window);
   DCHECK(frame_view);
@@ -173,7 +210,7 @@
       base::BindOnce(&CompatModeButtonController::UpdateArrowIcon,
                      base::Unretained(this), window,
                      /*widget_visibility=*/false),
-      frame_view->frame(), pref_delegate);
+      frame_view->frame(), pref_delegate_);
   UpdateArrowIcon(window, /*widget_visibility=*/true);
 }
 
diff --git a/ash/components/arc/compat_mode/compat_mode_button_controller.h b/ash/components/arc/compat_mode/compat_mode_button_controller.h
index fd436cfa..a568810 100644
--- a/ash/components/arc/compat_mode/compat_mode_button_controller.h
+++ b/ash/components/arc/compat_mode/compat_mode_button_controller.h
@@ -6,8 +6,10 @@
 #define ASH_COMPONENTS_ARC_COMPAT_MODE_COMPAT_MODE_BUTTON_CONTROLLER_H_
 
 #include <memory>
+#include <optional>
 
 #include "ash/components/arc/compat_mode/resize_toggle_menu.h"
+#include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 
 namespace ash {
@@ -28,6 +30,19 @@
 
 class CompatModeButtonController {
  public:
+  // Struct that represents the state for the `CompatModeButton` or a similar
+  // view.
+  struct ButtonState {
+    ButtonState();
+    explicit ButtonState(bool enable);
+    ButtonState(bool enable, const std::u16string& tooltip_text);
+    ButtonState(const ButtonState& other);
+    ~ButtonState();
+
+    bool enable;  // Whether to enable the button.
+    absl::optional<std::u16string> tooltip_text;  // The button's tooltip text.
+  };
+
   CompatModeButtonController();
   CompatModeButtonController(const CompatModeButtonController&) = delete;
   CompatModeButtonController& operator=(const CompatModeButtonController&) =
@@ -35,12 +50,24 @@
   virtual ~CompatModeButtonController();
 
   // virtual for unittest.
-  virtual void Update(ArcResizeLockPrefDelegate* pref_delegate,
-                      aura::Window* window);
+  virtual void Update(aura::Window* window);
 
   // virtual for unittest.
   virtual void OnButtonPressed();
 
+  // Clears the `pref_delegate_`. Called when `ArcResizeLockManager` keyed
+  // service is shutting down, so the delegate isn't being freed with invalid
+  // memory.
+  void ClearPrefDelegate();
+
+  // Sets `pref_delegate_` to `delegate`, ensuring that it was not already set.
+  void SetPrefDelegate(ArcResizeLockPrefDelegate* pref_delegate);
+
+  // Using the `window`'s `ash::kArcResizeLockTypeKey` window property, returns
+  // the updated `ButtonState` for the `CompatModeButton`, or a similar view. If
+  // the button should not be updated, then it returns `std::nullopt`.
+  std::optional<ButtonState> GetButtonState(aura::Window* window);
+
   void UpdateArrowIcon(aura::Window* window, bool widget_visibility);
 
   base::WeakPtr<CompatModeButtonController> GetWeakPtr();
@@ -49,14 +76,14 @@
   // virtual for unittest.
   virtual chromeos::FrameHeader* GetFrameHeader(aura::Window* window);
 
-  void UpdateAshAccelerator(ArcResizeLockPrefDelegate* pref_delegate,
-                            aura::Window* window);
+  void UpdateAshAccelerator(aura::Window* window);
 
-  void ToggleResizeToggleMenu(aura::Window* window,
-                              ArcResizeLockPrefDelegate* pref_delegate);
+  void ToggleResizeToggleMenu(aura::Window* window);
 
   std::unique_ptr<ResizeToggleMenu> resize_toggle_menu_;
 
+  raw_ptr<ArcResizeLockPrefDelegate, ExperimentalAsh> pref_delegate_;
+
   bool visible_when_button_pressed_{false};
 
   base::WeakPtrFactory<CompatModeButtonController> weak_ptr_factory_{this};
diff --git a/ash/components/arc/compat_mode/compat_mode_button_controller_unittest.cc b/ash/components/arc/compat_mode/compat_mode_button_controller_unittest.cc
index d7366d46..64ce74f 100644
--- a/ash/components/arc/compat_mode/compat_mode_button_controller_unittest.cc
+++ b/ash/components/arc/compat_mode/compat_mode_button_controller_unittest.cc
@@ -70,6 +70,7 @@
     widget_ = CreateArcWidget(/*app_id=*/absl::nullopt);
     controller_.set_frame_header(
         std::make_unique<FakeFrameHeader>(widget_.get()));
+    controller_.SetPrefDelegate(pref_delegate());
   }
   void TearDown() override {
     widget_->CloseNow();
@@ -90,7 +91,7 @@
 TEST_F(CompatModeButtonControllerTest, UpdateWithoutAppId) {
   const auto* frame_header = controller()->GetFrameHeader(window());
 
-  controller()->Update(pref_delegate(), window());
+  controller()->Update(window());
   EXPECT_FALSE(frame_header->GetCenterButton());
 }
 
@@ -103,7 +104,7 @@
   pref_delegate()->SetResizeLockState(app_id,
                                       mojom::ArcResizeLockState::UNDEFINED);
   SyncResizeLockPropertyWithMojoState(widget());
-  controller()->Update(pref_delegate(), window());
+  controller()->Update(window());
   EXPECT_FALSE(frame_header->GetCenterButton());
 }
 
@@ -115,7 +116,7 @@
 
   pref_delegate()->SetResizeLockState(app_id, mojom::ArcResizeLockState::READY);
   SyncResizeLockPropertyWithMojoState(widget());
-  controller()->Update(pref_delegate(), window());
+  controller()->Update(window());
   EXPECT_FALSE(frame_header->GetCenterButton());
 }
 
@@ -129,12 +130,12 @@
   SyncResizeLockPropertyWithMojoState(widget());
   // Phone
   ResizeLockToPhone(widget(), pref_delegate());
-  controller()->Update(pref_delegate(), window());
+  controller()->Update(window());
   EXPECT_TRUE(frame_header->GetCenterButton());
   EXPECT_TRUE(frame_header->GetCenterButton()->GetEnabled());
   // Tablet
   ResizeLockToTablet(widget(), pref_delegate());
-  controller()->Update(pref_delegate(), window());
+  controller()->Update(window());
   EXPECT_TRUE(frame_header->GetCenterButton());
   EXPECT_TRUE(frame_header->GetCenterButton()->GetEnabled());
 }
@@ -147,7 +148,7 @@
 
   pref_delegate()->SetResizeLockState(app_id, mojom::ArcResizeLockState::OFF);
   SyncResizeLockPropertyWithMojoState(widget());
-  controller()->Update(pref_delegate(), window());
+  controller()->Update(window());
   EXPECT_TRUE(frame_header->GetCenterButton());
   EXPECT_TRUE(frame_header->GetCenterButton()->GetEnabled());
 }
@@ -164,12 +165,12 @@
   SyncResizeLockPropertyWithMojoState(widget());
   // Phone
   ResizeLockToPhone(widget(), pref_delegate());
-  controller()->Update(pref_delegate(), window());
+  controller()->Update(window());
   EXPECT_TRUE(frame_header->GetCenterButton());
   EXPECT_FALSE(frame_header->GetCenterButton()->GetEnabled());
   // Tablet
   ResizeLockToTablet(widget(), pref_delegate());
-  controller()->Update(pref_delegate(), window());
+  controller()->Update(window());
   EXPECT_TRUE(frame_header->GetCenterButton());
   EXPECT_FALSE(frame_header->GetCenterButton()->GetEnabled());
 }
diff --git a/ash/display/display_manager_unittest.cc b/ash/display/display_manager_unittest.cc
index c5a42fb..794830c0 100644
--- a/ash/display/display_manager_unittest.cc
+++ b/ash/display/display_manager_unittest.cc
@@ -36,6 +36,7 @@
 #include "base/format_macros.h"
 #include "base/memory/raw_ptr.h"
 #include "base/numerics/math_constants.h"
+#include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
@@ -85,6 +86,80 @@
   return base::StringPrintf("Display-%d", static_cast<int>(id));
 }
 
+// Asserts that metrics propagated by DisplayManager and DisplayManagerObserver
+// are consistent.
+class DisplayManagerObserverValidator : public display::DisplayObserver,
+                                        public display::DisplayManagerObserver {
+ public:
+  DisplayManagerObserverValidator() {
+    display_observer_.emplace(this);
+    display_manager_observation_.Observe(Shell::Get()->display_manager());
+  }
+
+  // display::DisplayObserver:
+  void OnDisplayAdded(const display::Display& new_display) override {
+    if (!base::Contains(added_displays_, new_display)) {
+      added_displays_.push_back(new_display);
+    }
+  }
+  void OnDisplayRemoved(const display::Display& old_display) override {
+    if (!base::Contains(added_displays_, old_display)) {
+      removed_displays_.push_back(old_display);
+    }
+  }
+  void OnDisplayMetricsChanged(const display::Display& display,
+                               uint32_t changed_metrics) override {
+    if (!base::Contains(changed_displays_, display)) {
+      changed_displays_.push_back(display);
+    }
+    if (!changed_metrics_.try_emplace(display.id(), changed_metrics).second) {
+      changed_metrics_[display.id()] |= changed_metrics;
+    }
+  }
+
+  // display::DisplayManager::Observer:
+  void OnWillProcessDisplayChanges() override {
+    // There should not be multiple OnWillProcessDisplayChanges() calls before
+    // the subsequent call to OnDidProcessDisplayChanges().
+    EXPECT_FALSE(processing_display_changes_);
+    processing_display_changes_ = true;
+  }
+  void OnDidProcessDisplayChanges(
+      const DisplayConfigurationChange& configuration_change) override {
+    EXPECT_TRUE(processing_display_changes_);
+
+    EXPECT_TRUE(base::ranges::is_permutation(
+        added_displays_, configuration_change.added_displays));
+    EXPECT_TRUE(base::ranges::is_permutation(
+        removed_displays_, configuration_change.removed_displays));
+
+    EXPECT_EQ(changed_metrics_.size(),
+              configuration_change.display_metrics_changes.size());
+    for (const auto& change : configuration_change.display_metrics_changes) {
+      EXPECT_TRUE(base::Contains(changed_metrics_, change.display->id()));
+      EXPECT_EQ(changed_metrics_[change.display->id()], change.changed_metrics);
+    }
+
+    processing_display_changes_ = false;
+    added_displays_.clear();
+    removed_displays_.clear();
+    changed_displays_.clear();
+    changed_metrics_.clear();
+  }
+
+ private:
+  bool processing_display_changes_ = false;
+  vector<display::Display> added_displays_;
+  vector<display::Display> removed_displays_;
+  vector<display::Display> changed_displays_;
+  base::flat_map<int64_t, uint32_t> changed_metrics_;
+
+  absl::optional<display::ScopedDisplayObserver> display_observer_;
+  base::ScopedObservation<display::DisplayManager,
+                          display::DisplayManagerObserver>
+      display_manager_observation_{this};
+};
+
 }  // namespace
 
 class DisplayManagerTest : public AshTestBase,
@@ -104,6 +179,7 @@
     display_observer_.emplace(this);
     display_manager_observation_.Observe(Shell::Get()->display_manager());
     Shell::GetPrimaryRootWindow()->AddObserver(this);
+    display_manager_observer_validator_.emplace();
   }
   void TearDown() override {
     Shell::GetPrimaryRootWindow()->RemoveObserver(this);
@@ -174,7 +250,10 @@
 
   // display::DisplayManager::Observer:
   void OnWillProcessDisplayChanges() override { ++will_process_count_; }
-  void OnDidProcessDisplayChanges() override { ++did_process_count_; }
+  void OnDidProcessDisplayChanges(
+      const DisplayConfigurationChange& configuration_change) override {
+    ++did_process_count_;
+  }
 
   // aura::WindowObserver overrides:
   void OnWindowDestroying(aura::Window* window) override {
@@ -224,6 +303,9 @@
   base::flat_map<int64_t, uint32_t> changed_metrics_;
   bool check_root_window_on_destruction_ = true;
 
+  absl::optional<DisplayManagerObserverValidator>
+      display_manager_observer_validator_;
+
   absl::optional<display::ScopedDisplayObserver> display_observer_;
   base::ScopedObservation<display::DisplayManager,
                           display::DisplayManagerObserver>
diff --git a/ash/display/display_util.cc b/ash/display/display_util.cc
index 73c82cc..1ad2c56 100644
--- a/ash/display/display_util.cc
+++ b/ash/display/display_util.cc
@@ -60,12 +60,14 @@
 std::unique_ptr<MouseWarpController> CreateMouseWarpController(
     display::DisplayManager* manager,
     aura::Window* drag_source) {
-  if (manager->IsInUnifiedMode() && manager->num_connected_displays() >= 2)
+  if (manager->IsInUnifiedMode() && manager->num_connected_displays() >= 2) {
     return std::make_unique<UnifiedMouseWarpController>();
+  }
   // Extra check for |num_connected_displays()| is for SystemDisplayApiTest
   // that injects MockScreen.
-  if (manager->GetNumDisplays() < 2 || manager->num_connected_displays() < 2)
+  if (manager->GetNumDisplays() < 2 || manager->num_connected_displays() < 2) {
     return std::make_unique<NullMouseWarpController>();
+  }
   return std::make_unique<ExtendedMouseWarpController>(drag_source);
 }
 
@@ -172,8 +174,9 @@
           data,
           base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
               base::BindRepeating([](absl::optional<int> button_index) {
-                if (button_index)
+                if (button_index) {
                   NewWindowDelegate::GetInstance()->OpenFeedbackPage();
+                }
               })),
           kNotificationMonitorWarningIcon,
           message_center::SystemNotificationWarningLevel::WARNING);
@@ -185,8 +188,9 @@
   const std::vector<display::Display>& displays =
       display::Screen::GetScreen()->GetAllDisplays();
   for (const auto& display : displays) {
-    if (display.bounds().Contains(rect_in_screen))
+    if (display.bounds().Contains(rect_in_screen)) {
       return true;
+    }
   }
   return false;
 }
@@ -195,8 +199,9 @@
   std::string str = base::StringPrintf("%.2f", refresh_rate);
 
   // Remove the mantissa for whole numbers.
-  if (EndsWith(str, ".00", base::CompareCase::INSENSITIVE_ASCII))
+  if (EndsWith(str, ".00", base::CompareCase::INSENSITIVE_ASCII)) {
     str.erase(str.length() - 3);
+  }
 
   return base::UTF8ToUTF16(str);
 }
@@ -205,8 +210,9 @@
   message_center::NotificationList::Notifications notifications =
       message_center::MessageCenter::Get()->GetVisibleNotifications();
   for (auto* const notification : notifications) {
-    if (notification->id() == kDisplayErrorNotificationId)
+    if (notification->id() == kDisplayErrorNotificationId) {
       return notification->message();
+    }
   }
   return std::u16string();
 }
diff --git a/ash/display/screen_orientation_controller.cc b/ash/display/screen_orientation_controller.cc
index 79a410ab..17264088 100644
--- a/ash/display/screen_orientation_controller.cc
+++ b/ash/display/screen_orientation_controller.cc
@@ -441,7 +441,8 @@
   suspend_orientation_lock_refreshes_ = true;
 }
 
-void ScreenOrientationController::OnDidProcessDisplayChanges() {
+void ScreenOrientationController::OnDidProcessDisplayChanges(
+    const DisplayConfigurationChange& configuration_change) {
   suspend_orientation_lock_refreshes_ = false;
   if (is_orientation_lock_refresh_pending_) {
     // Note: We must set |is_orientation_lock_refresh_pending_| to false first
diff --git a/ash/display/screen_orientation_controller.h b/ash/display/screen_orientation_controller.h
index cd82db5..0776000 100644
--- a/ash/display/screen_orientation_controller.h
+++ b/ash/display/screen_orientation_controller.h
@@ -155,7 +155,8 @@
 
   // display::DisplayManagerObserver:
   void OnWillProcessDisplayChanges() override;
-  void OnDidProcessDisplayChanges() override;
+  void OnDidProcessDisplayChanges(
+      const DisplayConfigurationChange& configuration_change) override;
 
  private:
   friend class ScreenOrientationControllerTestApi;
diff --git a/ash/webui/common/resources/cellular_setup/cellular_setup.html b/ash/webui/common/resources/cellular_setup/cellular_setup.html
index 23df08b1..a23640fe 100644
--- a/ash/webui/common/resources/cellular_setup/cellular_setup.html
+++ b/ash/webui/common/resources/cellular_setup/cellular_setup.html
@@ -6,7 +6,7 @@
       if="[[shouldShowPsimFlow_(currentPageName)]]" restamp>
     <psim-flow-ui
         button-state="{{buttonState_}}"
-        name-of-carrier-pending-setup="{{flowTitle}}"
+        name-of-carrier-pending-setup="{{flowPsimBanner}}"
         delegate="[[delegate]]"
         id="psim-flow-ui"
         forward-button-label="{{forwardButtonLabel_}}">
diff --git a/ash/webui/common/resources/cellular_setup/cellular_setup.js b/ash/webui/common/resources/cellular_setup/cellular_setup.js
index 695fd74..d2b4c7d 100644
--- a/ash/webui/common/resources/cellular_setup/cellular_setup.js
+++ b/ash/webui/common/resources/cellular_setup/cellular_setup.js
@@ -28,10 +28,10 @@
     delegate: Object,
 
     /**
-     * Title of the flow, shown at the top of the dialog. No title shown if the
-     * string is empty.
+     * Banner used in pSIM flow to show carrier network name. No banner
+     * shown if the string is empty.
      */
-    flowTitle: {
+    flowPsimBanner: {
       type: String,
       notify: true,
       value: '',
@@ -110,7 +110,7 @@
   /** @private */
   onPageChange_() {
     if (this.currentPage_) {
-      this.flowTitle = '';
+      this.flowPsimBanner = '';
       this.currentPage_.initSubflow();
     }
   },
diff --git a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html
index 55942655..2ee91ee 100644
--- a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html
+++ b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html
@@ -56,12 +56,14 @@
       </shortcut-input-key>
       <shortcut-input-key id="searchKey"
           key="meta"
-          key-state="[[getSearchState(pendingKeyEvent.*)]]">
+          key-state="[[getSearchState(pendingKeyEvent.*)]]"
+          has-launcher-button="[[hasLauncherButton]]">
       </shortcut-input-key>
       <div id="keySeparator"> + </div>
       <shortcut-input-key id="pendingKey"
           key="[[getKey(pendingKeyEvent.*)]]"
-          key-state="[[getKeyState(pendingKeyEvent.*)]]">
+          key-state="[[getKeyState(pendingKeyEvent.*)]]"
+          has-launcher-button="[[hasLauncherButton]]">
       </shortcut-input-key>
     </div>
   </template>
diff --git a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.ts b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.ts
index c5de895..99db665 100644
--- a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.ts
+++ b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.ts
@@ -50,6 +50,17 @@
       showSeparator: {
         type: Boolean,
       },
+
+      hasLauncherButton: {
+        type: Boolean,
+      },
+
+      // When `updateOnKeyPress` is true, always show edit-view and and updates
+      // occur on key press events rather than on key release.
+      updateOnKeyPress: {
+        type: Boolean,
+        value: false,
+      },
     };
   }
 
@@ -59,6 +70,7 @@
   modifiers: Modifier[] = [];
   showSeparator: boolean = false;
   isCapturing: boolean = false;
+  updateOnKeyPress: boolean = false;
   private shortcutInputObserverReceiver: ShortcutInputObserverReceiver|null =
       null;
   private eventTracker: EventTracker = new EventTracker();
@@ -183,11 +195,12 @@
   }
 
   shouldShowEditView(): boolean {
-    return this.isCapturing;
+    return this.isCapturing || this.updateOnKeyPress;
   }
 
   shouldShowConfirmView(): boolean {
-    return this.pendingKeyEvent !== null && !this.isCapturing;
+    return this.pendingKeyEvent !== null && !this.isCapturing &&
+        !this.updateOnKeyPress;
   }
 
   /**
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html
index bb667dc2..d2ca324f 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html
+++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.html
@@ -61,40 +61,8 @@
     </div>
   </template>
   <template is="dom-if" if="[[showEditView(viewState)]]">
-    <!-- TODO(longbowei): Update to use shortcut-input element -->
-    <div id="editContainer" class="flex-row">
-      <shortcut-input-key id="ctrlKey" key="ctrl"
-          key-state="[[getCtrlState(
-            pendingAcceleratorInfo.layoutProperties.standardAccelerator.accelerator.*
-            )]]">
-      </shortcut-input-key>
-      <shortcut-input-key id="altKey" key="alt"
-          key-state="[[getAltState(
-            pendingAcceleratorInfo.layoutProperties.standardAccelerator.*
-            )]]">
-      </shortcut-input-key>
-      <shortcut-input-key id="shiftKey" key="shift"
-          key-state="[[getShiftState(
-            pendingAcceleratorInfo.layoutProperties.standardAccelerator.*
-            )]]">
-      </shortcut-input-key>
-      <shortcut-input-key id="searchKey" key="meta"
-          key-state="[[getSearchState(
-            pendingAcceleratorInfo.layoutProperties.standardAccelerator.*
-            )]]"
-          has-launcher-button="[[hasLauncherButton]]">
-      </shortcut-input-key>
-      <div id="acceleratorSeparator" aria-hidden="true"> + </div>
-      <shortcut-input-key id="pendingKey"
-          key="[[getPendingKey(
-            pendingAcceleratorInfo.layoutProperties.standardAccelerator.*
-            )]]"
-          key-state=
-              "[[getPendingKeyState(
-                pendingAcceleratorInfo.layoutProperties.standardAccelerator.*
-                )]]"
-          has-launcher-button="[[hasLauncherButton]]">
-      </shortcut-input-key>
-    </div>
+    <shortcut-input id="shortcut-input" pending-key-event="[[pendingKeyEvent]]"
+        has-launcher-button="[[hasLauncherButton]]" update-on-key-press="true">
+    </shortcut-input>
   </template>
 </div>
\ No newline at end of file
diff --git a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
index eeaf1ae..40cc629e 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
+++ b/ash/webui/shortcut_customization_ui/resources/js/accelerator_view.ts
@@ -4,8 +4,10 @@
 
 import 'chrome://resources/cr_elements/cr_input/cr_input.js';
 import 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input_key.js';
+import 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input.js';
 
-import {ModifierKeyCodes} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_utils.js';
+import {VKey as ash_mojom_VKey} from 'chrome://resources/ash/common/shortcut_input_ui/accelerator_keys.mojom-webui.js';
+import {KeyEvent} from 'chrome://resources/ash/common/shortcut_input_ui/input_device_settings.mojom-webui.js';
 import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
 import {getInstance as getAnnouncerInstance} from 'chrome://resources/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
@@ -21,20 +23,15 @@
 import {AcceleratorLookupManager} from './accelerator_lookup_manager.js';
 import {getTemplate} from './accelerator_view.html.js';
 import {getShortcutProvider} from './mojo_interface_provider.js';
-import {Accelerator, AcceleratorConfigResult, AcceleratorKeyState, AcceleratorSource, AcceleratorState, EditAction, Modifier, ShortcutProviderInterface, StandardAcceleratorInfo} from './shortcut_types.js';
-import {areAcceleratorsEqual, canBypassErrorWithRetry, createEmptyAcceleratorInfo, getAccelerator, getKeyDisplay, getModifiersForAcceleratorInfo, isCustomizationAllowed, isFunctionKey, isStandardAcceleratorInfo, keyCodeToModifier, keyToIconNameMap, LWIN_KEY, META_KEY, unidentifiedKeyCodeToKey} from './shortcut_utils.js';
+import {Accelerator, AcceleratorConfigResult, AcceleratorSource, AcceleratorState, EditAction, Modifier, ShortcutProviderInterface, StandardAcceleratorInfo} from './shortcut_types.js';
+import {areAcceleratorsEqual, canBypassErrorWithRetry, getAccelerator, getKeyDisplay, getModifiersForAcceleratorInfo, isCustomizationAllowed, isModifierKey, isStandardAcceleratorInfo, isValidDefaultAccelerator, keyCodeToModifier, keystrokeToAccelerator, keystrokeToKeyEvent, LWIN_KEY, META_KEY, resetKeyEvent} from './shortcut_utils.js';
+
 export interface AcceleratorViewElement {
   $: {
     container: HTMLDivElement,
   };
 }
 
-enum KeyState {
-  NOT_SELECTED = 'not-selected',
-  MODIFIER = 'modifier-selected',
-  ALPHANUMERIC = 'alpha-numeric-selected',
-}
-
 export enum ViewState {
   VIEW,
   ADD,
@@ -67,7 +64,7 @@
         type: Object,
       },
 
-      pendingAcceleratorInfo: {
+      pendingKeyEvent: {
         type: Object,
       },
 
@@ -162,7 +159,7 @@
   isFirstAccelerator: boolean;
   isDisabled: boolean;
   hasLauncherButton: boolean;
-  protected pendingAcceleratorInfo: StandardAcceleratorInfo;
+  pendingKeyEvent: KeyEvent|null = null;
   protected isCapturing: boolean;
   protected lastAccelerator: Accelerator;
   protected lastResult: AcceleratorConfigResult;
@@ -211,13 +208,12 @@
     this.eventTracker.removeAll();
   }
 
-
   private async startCapture(): Promise<void> {
     if (this.isCapturing) {
       return;
     }
     // Disable ChromeOS accelerator handler when starting input capture.
-    this.pendingAcceleratorInfo = createEmptyAcceleratorInfo();
+    this.pendingKeyEvent = resetKeyEvent();
     this.isCapturing = true;
 
     this.dispatchEvent(new CustomEvent('accelerator-capturing-started', {
@@ -260,7 +256,7 @@
     // is dependent on `hasError`'s state.
     this.hasError = false;
     this.statusMessage = '';
-    this.pendingAcceleratorInfo = createEmptyAcceleratorInfo();
+    this.pendingKeyEvent = resetKeyEvent();
   }
 
   private onKeyDown(e: KeyboardEvent): void {
@@ -284,7 +280,7 @@
   }
 
   private handleKeyDown(e: KeyboardEvent): void {
-    const pendingAccelerator = this.keystrokeToAccelerator(e);
+    const pendingAccelerator = keystrokeToAccelerator(e);
     if (this.hasError) {
       // If an error occurred, check if the pending accelerator matches the
       // last. If they match and a retry on the same accelerator
@@ -307,62 +303,39 @@
       return;
     }
 
-    // Add the key pressed to pendingAccelerator.
-    this.set(
-        'pendingAcceleratorInfo.layoutProperties.standardAccelerator.accelerator',
-        pendingAccelerator);
-
-    if (this.isModifierKey(e)) {
-      // Reset the keyDisplay property if the key is a modifier.
-      this.set(
-          'pendingAcceleratorInfo.layoutProperties.standardAccelerator.keyDisplay',
-          '');
-    } else {
-      // Set keyDisplay property.
-      this.set(
-          'pendingAcceleratorInfo.layoutProperties.standardAccelerator.keyDisplay',
-          this.getKeyDisplay(e));
-    }
+    // Add the key pressed to pendingKeyEvent.
+    this.pendingKeyEvent = keystrokeToKeyEvent(e);
 
     // Only process valid accelerators.
-    if (this.isValidDefaultAccelerator(this.pendingAcceleratorInfo)) {
-      this.processPendingAccelerator(this.pendingAcceleratorInfo);
+    if (isValidDefaultAccelerator(pendingAccelerator)) {
+      this.processPendingAccelerator(pendingAccelerator);
     }
   }
 
   private handleKeyUp(e: KeyboardEvent): void {
-    const pendingAccelerator = this.pendingAcceleratorInfo.layoutProperties
-                                   .standardAccelerator.accelerator;
+    const updatedKeyEvent = {...this.pendingKeyEvent};
+
     // Remove the modifier that was just released.
-    if (this.isModifierKey(e)) {
+    if (isModifierKey(e.keyCode)) {
       const modifier = keyCodeToModifier[e.keyCode];
-      const pendingModifiers = pendingAccelerator.modifiers;
+      const pendingModifiers = this.pendingKeyEvent!.modifiers;
       // Assert that the released modifier is present in the pending
       // accelerator.
       assert(pendingModifiers & modifier);
       // Remove the released modifier.
-      const updatedModifiers = pendingModifiers - modifier;
-      this.set(
-          'pendingAcceleratorInfo.layoutProperties.standardAccelerator.' +
-              'accelerator.modifiers',
-          updatedModifiers);
+      updatedKeyEvent.modifiers = pendingModifiers - modifier;
     } else {
       // Remove the key that was just released.
-      const updatedAccelerator = pendingAccelerator;
-      updatedAccelerator.keyCode = 0;
-      this.set(
-          'pendingAcceleratorInfo.layoutProperties.standardAccelerator.' +
-              'accelerator',
-          updatedAccelerator);
-      this.set(
-          'pendingAcceleratorInfo.layoutProperties.standardAccelerator' +
-              '.keyDisplay',
-          '');
+      updatedKeyEvent.vkey = ash_mojom_VKey.MIN_VALUE;
+      updatedKeyEvent.keyDisplay = '';
     }
+
+    // Update pendingKeyEvent.
+    this.set('pendingKeyEvent', updatedKeyEvent);
   }
 
-  private async processPendingAccelerator(
-      pendingAccelInfo: StandardAcceleratorInfo): Promise<void> {
+  private async processPendingAccelerator(pendingAccelerator: Accelerator):
+      Promise<void> {
     // Dispatch an event indicating that accelerator update is in progress.
     this.dispatchEvent(new CustomEvent('accelerator-update-in-progress', {
       bubbles: true,
@@ -382,7 +355,7 @@
     if (this.viewState === ViewState.ADD || isDisabledAccelerator) {
       this.editAction = EditAction.ADD;
       result = await this.shortcutProvider.addAccelerator(
-          this.source, this.action, getAccelerator(pendingAccelInfo));
+          this.source, this.action, pendingAccelerator);
     }
 
     if (this.viewState === ViewState.EDIT && !isDisabledAccelerator) {
@@ -393,8 +366,7 @@
       const acceleratorToEdit =
           originalAccelerator || getAccelerator(this.acceleratorInfo);
       result = await this.shortcutProvider.replaceAccelerator(
-          this.source, this.action, acceleratorToEdit,
-          getAccelerator(pendingAccelInfo));
+          this.source, this.action, acceleratorToEdit, pendingAccelerator);
     }
     this.handleAcceleratorResultData(result!.result);
   }
@@ -470,8 +442,7 @@
       case AcceleratorConfigResult.kReservedKeyNotAllowed: {
         this.statusMessage = this.i18n(
             'reservedKeyNotAllowedStatusMessage',
-            this.pendingAcceleratorInfo.layoutProperties.standardAccelerator
-                .keyDisplay);
+            this.pendingKeyEvent!.keyDisplay);
         this.hasError = true;
         this.makeA11yAnnouncement(this.statusMessage);
         return;
@@ -495,67 +466,6 @@
     assertNotReached();
   }
 
-  /**
-   * Converts a keystroke event to an Accelerator Object.
-   */
-  private keystrokeToAccelerator(e: KeyboardEvent): Accelerator {
-    const output: Accelerator = {
-      modifiers: 0,
-      keyCode: 0,
-      keyState: AcceleratorKeyState.PRESSED,
-    };
-    if (e.metaKey) {
-      output.modifiers = output.modifiers | Modifier.COMMAND;
-    }
-    if (e.ctrlKey) {
-      output.modifiers = output.modifiers | Modifier.CONTROL;
-    }
-    if (e.altKey) {
-      output.modifiers = output.modifiers | Modifier.ALT;
-    }
-    // Shift key isn't registered as a modifier unless a non-modifer key is
-    // pressed in conjunction with the keystroke.
-    if (e.key == 'Shift' || e.shiftKey) {
-      output.modifiers = output.modifiers | Modifier.SHIFT;
-    }
-
-    // Only add non-modifier or function keys as the pending key.
-    if (!this.isModifierKey(e) || isFunctionKey(e.keyCode)) {
-      output.keyCode = e.keyCode;
-    }
-
-    return output;
-  }
-
-  private getKeyDisplay(e: KeyboardEvent): string {
-    switch (e.code) {
-      case 'Space':  // Space key: e.key: ' ', e.code: 'Space', set keyDisplay
-                     // to be 'space' text.
-        return 'space';
-      case 'ShowAllWindows':  // Overview key: e.key: 'F4', e.code:
-                              // 'ShowAllWindows', set keyDisplay to be
-                              // 'LaunchApplication1' and will display as
-                              // 'overview' icon.
-        return 'LaunchApplication1';
-      case 'Backquote':
-        // Backquote `key` will become 'unidentified' when ctrl
-        // is pressed.
-        if (e.ctrlKey && e.key === 'Unidentified') {
-          return unidentifiedKeyCodeToKey[e.keyCode];
-        }
-        return e.key;
-      case '':
-        // If there is no `code`, check the `key`. If the `key` is
-        // `unidentified`, we need to manually lookup the key.
-        return unidentifiedKeyCodeToKey[e.keyCode] || e.key;
-      default:  // All other keys: Use the original e.key as keyDisplay.
-        return e.key;
-    }
-  }
-
-  private isModifierKey(e: KeyboardEvent): boolean {
-    return ModifierKeyCodes.includes(e.keyCode);
-  }
 
   private makeA11yAnnouncement(message: string): void {
     const announcer = getAnnouncerInstance(this.$.container);
@@ -567,98 +477,6 @@
     announcer.announce(message);
   }
 
-  /**
-   * Returns the specified CSS state of the modifier key element.
-   */
-  protected getCtrlState(): string {
-    return this.getModifierState(Modifier.CONTROL);
-  }
-
-  /**
-   * Returns the specified CSS state of the modifier key element.
-   */
-  protected getAltState(): string {
-    return this.getModifierState(Modifier.ALT);
-  }
-
-  /**
-   * Returns the specified CSS state of the modifier key element.
-   */
-  protected getShiftState(): string {
-    return this.getModifierState(Modifier.SHIFT);
-  }
-
-  /**
-   * Returns the specified CSS state of the modifier key element.
-   */
-  protected getSearchState(): string {
-    return this.getModifierState(Modifier.COMMAND);
-  }
-
-  /**
-   * Returns the specified CSS state of the modifier key element.
-   */
-  private getModifierState(modifier: Modifier): KeyState {
-    // If the accelerator is disabled, we default to the `NOT_SELECTED` state if
-    // the user is not editing the accelerator.
-    if (this.isDisabled && this.viewState !== ViewState.EDIT) {
-      return KeyState.NOT_SELECTED;
-    }
-
-    if ((getAccelerator(this.pendingAcceleratorInfo)).modifiers & modifier) {
-      return KeyState.MODIFIER;
-    }
-    return KeyState.NOT_SELECTED;
-  }
-
-  /**
-   * Returns the specified CSS state of the pending key element.
-   */
-  protected getPendingKeyState(): string {
-    if (this.pendingAcceleratorInfo.layoutProperties.standardAccelerator
-            .keyDisplay != '') {
-      return KeyState.ALPHANUMERIC;
-    }
-    return KeyState.NOT_SELECTED;
-  }
-
-  /**
-   * Returns the specified key to display.
-   */
-  protected getPendingKey(): string {
-    if (this.pendingAcceleratorInfo.layoutProperties.standardAccelerator
-            .keyDisplay != '') {
-      const keyDisplay = this.pendingAcceleratorInfo.layoutProperties
-                             .standardAccelerator.keyDisplay;
-      // Display as icon if it exists in the map.
-      if (keyDisplay in keyToIconNameMap) {
-        return keyDisplay;
-      }
-      return keyDisplay.toLowerCase();
-    }
-    return this.i18n('inputKeyPlaceholder');
-  }
-
-  /**
-   * Returns true if the event has valid modifiers.
-   */
-  private hasValidModifiers(e: KeyboardEvent): boolean {
-    // Although Shift is a modifier, it cannot be a standalone modifier for a
-    // shortcut.
-    return e.ctrlKey || e.altKey || e.metaKey;
-  }
-
-  private isValidDefaultAccelerator(accelInfo: StandardAcceleratorInfo):
-      boolean {
-    // A valid default accelerator is one that has modifier(s) and a key or
-    // is function key.
-    const accelerator =
-        accelInfo.layoutProperties.standardAccelerator.accelerator;
-    return (accelerator.modifiers > 0 &&
-            accelInfo.layoutProperties.standardAccelerator.keyDisplay !== '') ||
-        isFunctionKey(accelerator.keyCode);
-  }
-
   private showEditView(): boolean {
     return this.viewState !== ViewState.VIEW;
   }
diff --git a/ash/webui/shortcut_customization_ui/resources/js/shortcut_utils.ts b/ash/webui/shortcut_customization_ui/resources/js/shortcut_utils.ts
index 883f6d6..51747fe 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/shortcut_utils.ts
+++ b/ash/webui/shortcut_customization_ui/resources/js/shortcut_utils.ts
@@ -5,6 +5,9 @@
 import '../strings.m.js';
 
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
+import {VKey as ash_mojom_VKey} from 'chrome://resources/ash/common/shortcut_input_ui/accelerator_keys.mojom-webui.js';
+import {KeyEvent} from 'chrome://resources/ash/common/shortcut_input_ui/input_device_settings.mojom-webui.js';
+import {ModifierKeyCodes} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_utils.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 import {mojoString16ToString} from 'chrome://resources/js/mojo_type_util.js';
 
@@ -121,6 +124,16 @@
       {modifiers: 0, keyCode: 0, keyState: AcceleratorKeyState.PRESSED});
 };
 
+export const resetKeyEvent = (): KeyEvent => {
+  return {
+    vkey: ash_mojom_VKey.MIN_VALUE,
+    domCode: 0,
+    domKey: 0,
+    modifiers: 0,
+    keyDisplay: '',
+  };
+};
+
 export const getAcceleratorId =
     (source: string|number, actionId: string|number): AcceleratorId => {
       return `${source}-${actionId}`;
@@ -332,6 +345,18 @@
   return keycode >= kF11 && keycode <= kF24;
 };
 
+export const isModifierKey = (keycode: number): boolean => {
+  return ModifierKeyCodes.includes(keycode);
+};
+
+export const isValidDefaultAccelerator =
+    (accelerator: Accelerator): boolean => {
+      // A valid default accelerator is one that has modifier(s) and a key or
+      // is function key.
+      return (accelerator.modifiers > 0 && accelerator.keyCode > 0) ||
+          isFunctionKey(accelerator.keyCode);
+    };
+
 export const getSourceAndActionFromAcceleratorId =
     (uuid: AcceleratorId): {source: number, action: number} => {
       // Split '{source}-{action}` into [source][action].
@@ -401,3 +426,88 @@
       assert(isTextAcceleratorInfo(textAcceleratorInfo));
       return textAcceleratorInfo.layoutProperties.textAccelerator.parts;
     };
+
+export const getModifiersFromKeyboardEvent = (e: KeyboardEvent): Modifier => {
+  let modifiers = 0;
+  if (e.metaKey) {
+    modifiers |= Modifier.COMMAND;
+  }
+  if (e.ctrlKey) {
+    modifiers |= Modifier.CONTROL;
+  }
+  if (e.altKey) {
+    modifiers |= Modifier.ALT;
+  }
+  if (e.key == 'Shift' || e.shiftKey) {
+    modifiers |= Modifier.SHIFT;
+  }
+  return modifiers;
+};
+
+export const getKeyDisplayFromKeyboardEvent = (e: KeyboardEvent): string => {
+  switch (e.code) {
+    case 'Space':  // Space key: e.key: ' ', e.code: 'Space', set keyDisplay
+      // to be 'space' text.
+      return 'space';
+    case 'ShowAllWindows':  // Overview key: e.key: 'F4', e.code:
+      // 'ShowAllWindows', set keyDisplay to be
+      // 'LaunchApplication1' and will display as
+      // 'overview' icon.
+      return 'LaunchApplication1';
+    case 'Backquote':
+      // Backquote `key` will become 'unidentified' when ctrl
+      // is pressed.
+      if (e.ctrlKey && e.key === 'Unidentified') {
+        return unidentifiedKeyCodeToKey[e.keyCode];
+      }
+      return e.key;
+    case '':
+      // If there is no `code`, check the `key`. If the `key` is
+      // `unidentified`, we need to manually lookup the key.
+      return unidentifiedKeyCodeToKey[e.keyCode] || e.key;
+    default:  // All other keys: Use the original e.key as keyDisplay.
+      return e.key;
+  }
+};
+
+/**
+ * Converts a keystroke event to an Accelerator Object.
+ */
+export const keystrokeToAccelerator = (e: KeyboardEvent): Accelerator => {
+  const output: Accelerator = {
+    modifiers: 0,
+    keyCode: 0,
+    keyState: AcceleratorKeyState.PRESSED,
+  };
+  output.modifiers = getModifiersFromKeyboardEvent(e);
+
+  // Only add non-modifier or function keys as the pending key.
+  if (!isModifierKey(e.keyCode) || isFunctionKey(e.keyCode)) {
+    output.keyCode = e.keyCode;
+  }
+
+  return output;
+};
+
+/**
+ * Converts a keystroke event to an KeyEvent Object.
+ */
+export const keystrokeToKeyEvent = (e: KeyboardEvent): KeyEvent => {
+  const output: KeyEvent = {
+    vkey: ash_mojom_VKey.MIN_VALUE,
+    domCode: 0,
+    domKey: 0,
+    modifiers: 0,
+    keyDisplay: '',
+  };
+  output.modifiers = getModifiersFromKeyboardEvent(e);
+
+  // Only add non-modifier or function keys as the pending key.
+  if (!isModifierKey(e.keyCode) || isFunctionKey(e.keyCode)) {
+    output.vkey = e.keyCode as ash_mojom_VKey;
+  }
+
+  output.keyDisplay =
+      isModifierKey(e.keyCode) ? '' : getKeyDisplayFromKeyboardEvent(e);
+  return output;
+};
diff --git a/ash/wm/multi_display/persistent_window_controller.cc b/ash/wm/multi_display/persistent_window_controller.cc
index d84f9a7..bfd982a 100644
--- a/ash/wm/multi_display/persistent_window_controller.cc
+++ b/ash/wm/multi_display/persistent_window_controller.cc
@@ -199,7 +199,8 @@
   }
 }
 
-void PersistentWindowController::OnDidProcessDisplayChanges() {
+void PersistentWindowController::OnDidProcessDisplayChanges(
+    const DisplayConfigurationChange& configuration_change) {
   if (display_added_restore_callback_) {
     std::move(display_added_restore_callback_).Run();
   }
diff --git a/ash/wm/multi_display/persistent_window_controller.h b/ash/wm/multi_display/persistent_window_controller.h
index fe5b684..ffff9bf 100644
--- a/ash/wm/multi_display/persistent_window_controller.h
+++ b/ash/wm/multi_display/persistent_window_controller.h
@@ -86,7 +86,8 @@
 
   // display::DisplayManagerObserver:
   void OnWillProcessDisplayChanges() override;
-  void OnDidProcessDisplayChanges() override;
+  void OnDidProcessDisplayChanges(
+      const DisplayConfigurationChange& configuration_change) override;
 
   // SessionObserver:
   void OnFirstSessionStarted() override;
diff --git a/ash/wm/window_cycle/window_cycle_item_view.cc b/ash/wm/window_cycle/window_cycle_item_view.cc
index dc82f32..00af1a9 100644
--- a/ash/wm/window_cycle/window_cycle_item_view.cc
+++ b/ash/wm/window_cycle/window_cycle_item_view.cc
@@ -36,7 +36,7 @@
 constexpr auto kInsideContainerBorderInset = gfx::Insets(2);
 
 // Spacing between the `WindowCycleItemView`s hosted by the container view.
-constexpr int kBetweenCycleItemsSpacing = 2;
+constexpr int kBetweenCycleItemsSpacing = 4;
 
 }  // namespace
 
diff --git a/base/android/build_info.h b/base/android/build_info.h
index 12e42d19..add851e0 100644
--- a/base/android/build_info.h
+++ b/base/android/build_info.h
@@ -13,8 +13,7 @@
 #include "base/base_export.h"
 #include "base/memory/singleton.h"
 
-namespace base {
-namespace android {
+namespace base::android {
 
 // This enumeration maps to the values returned by BuildInfo::sdk_int(),
 // indicating the Android release associated with a given SDK version.
@@ -37,6 +36,7 @@
   SDK_VERSION_S = 31,
   SDK_VERSION_Sv2 = 32,
   SDK_VERSION_T = 33,
+  SDK_VERSION_U = 34,
 };
 
 // BuildInfo is a singleton class that stores android build and device
@@ -206,7 +206,6 @@
   const int32_t vulkan_deqp_level_;
 };
 
-}  // namespace android
-}  // namespace base
+}  // namespace base::android
 
 #endif  // BASE_ANDROID_BUILD_INFO_H_
diff --git a/base/functional/callback.h b/base/functional/callback.h
index f6a52fc..3e8e6fd 100644
--- a/base/functional/callback.h
+++ b/base/functional/callback.h
@@ -10,6 +10,8 @@
 #define BASE_FUNCTIONAL_CALLBACK_H_
 
 #include <stddef.h>
+
+#include <type_traits>
 #include <utility>
 
 #include "base/check.h"
@@ -206,40 +208,41 @@
     return *this;
   }
 
-  template <typename S = R, typename = std::enable_if_t<std::is_void_v<S>>>
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr OnceCallback(internal::DoNothingCallbackTag)
+    requires(std::is_void_v<R>)
       : OnceCallback(BindOnce([](Args... args) {})) {}
-  template <typename S = R, typename = std::enable_if_t<std::is_void_v<S>>>
-  constexpr OnceCallback& operator=(internal::DoNothingCallbackTag) {
+  constexpr OnceCallback& operator=(internal::DoNothingCallbackTag)
+    requires(std::is_void_v<R>)
+  {
     *this = BindOnce([](Args... args) {});
     return *this;
   }
 
-  template <typename S = R, typename = std::enable_if_t<std::is_void_v<S>>>
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr OnceCallback(internal::DoNothingCallbackTag::WithSignature<RunType>)
+    requires(std::is_void_v<R>)
       : OnceCallback(internal::DoNothingCallbackTag()) {}
-  template <typename S = R, typename = std::enable_if_t<std::is_void_v<S>>>
   constexpr OnceCallback& operator=(
-      internal::DoNothingCallbackTag::WithSignature<RunType>) {
+      internal::DoNothingCallbackTag::WithSignature<RunType>)
+    requires(std::is_void_v<R>)
+  {
     *this = internal::DoNothingCallbackTag();
     return *this;
   }
 
-  template <typename... BoundArgs,
-            typename S = R,
-            typename = std::enable_if_t<std::is_void_v<S>>>
+  template <typename... BoundArgs>
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr OnceCallback(
       internal::DoNothingCallbackTag::WithBoundArguments<BoundArgs...> tag)
+    requires(std::is_void_v<R>)
       : OnceCallback(
             internal::ToDoNothingCallback<true, R, Args...>(std::move(tag))) {}
-  template <typename... BoundArgs,
-            typename S = R,
-            typename = std::enable_if_t<std::is_void_v<S>>>
+  template <typename... BoundArgs>
   constexpr OnceCallback& operator=(
-      internal::DoNothingCallbackTag::WithBoundArguments<BoundArgs...> tag) {
+      internal::DoNothingCallbackTag::WithBoundArguments<BoundArgs...> tag)
+    requires(std::is_void_v<R>)
+  {
     *this = internal::ToDoNothingCallback<true, R, Args...>(std::move(tag));
     return *this;
   }
@@ -416,41 +419,42 @@
     return *this;
   }
 
-  template <typename S = R, typename = std::enable_if_t<std::is_void_v<S>>>
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr RepeatingCallback(internal::DoNothingCallbackTag)
+    requires(std::is_void_v<R>)
       : RepeatingCallback(BindRepeating([](Args... args) {})) {}
-  template <typename S = R, typename = std::enable_if_t<std::is_void_v<S>>>
-  constexpr RepeatingCallback& operator=(internal::DoNothingCallbackTag) {
+  constexpr RepeatingCallback& operator=(internal::DoNothingCallbackTag)
+    requires(std::is_void_v<R>)
+  {
     *this = BindRepeating([](Args... args) {});
     return *this;
   }
 
-  template <typename S = R, typename = std::enable_if_t<std::is_void_v<S>>>
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr RepeatingCallback(
       internal::DoNothingCallbackTag::WithSignature<RunType>)
+    requires(std::is_void_v<R>)
       : RepeatingCallback(internal::DoNothingCallbackTag()) {}
-  template <typename S = R, typename = std::enable_if_t<std::is_void_v<S>>>
   constexpr RepeatingCallback& operator=(
-      internal::DoNothingCallbackTag::WithSignature<RunType>) {
+      internal::DoNothingCallbackTag::WithSignature<RunType>)
+    requires(std::is_void_v<R>)
+  {
     *this = internal::DoNothingCallbackTag();
     return *this;
   }
 
-  template <typename... BoundArgs,
-            typename S = R,
-            typename = std::enable_if_t<std::is_void_v<S>>>
+  template <typename... BoundArgs>
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr RepeatingCallback(
       internal::DoNothingCallbackTag::WithBoundArguments<BoundArgs...> tag)
+    requires(std::is_void_v<R>)
       : RepeatingCallback(
             internal::ToDoNothingCallback<false, R, Args...>(std::move(tag))) {}
-  template <typename... BoundArgs,
-            typename S = R,
-            typename = std::enable_if_t<std::is_void_v<S>>>
+  template <typename... BoundArgs>
   constexpr RepeatingCallback& operator=(
-      internal::DoNothingCallbackTag::WithBoundArguments<BoundArgs...> tag) {
+      internal::DoNothingCallbackTag::WithBoundArguments<BoundArgs...> tag)
+    requires(std::is_void_v<R>)
+  {
     *this = internal::ToDoNothingCallback<false, R, Args...>(std::move(tag));
     return this;
   }
diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto
index 0cd1e4e..90cde7d 100644
--- a/base/tracing/protos/chrome_track_event.proto
+++ b/base/tracing/protos/chrome_track_event.proto
@@ -1449,6 +1449,10 @@
   optional bool is_slow_scroll = 6;
 };
 
+//
+// Critical User Interaction metrics
+//
+
 message PageLoad {
   optional int64 navigation_id = 1;
   optional string url = 2;
@@ -1482,9 +1486,21 @@
   optional LauchCauseType launch_cause = 2;
 }
 
+message WebContentInteraction {
+  enum Type {
+    INTERACTION_UNSPECIFIED = 0;
+    INTERACTION_KEYBOARD = 1;
+    INTERACTION_CLICK_TAP = 2;
+    INTERACTION_DRAG = 3;
+  }
+
+  optional Type type = 1;
+  optional int64 total_duration_ms = 2;
+}
+
 message ChromeTrackEvent {
   // Extension range for Chrome: 1000-1999
-  // Next ID: 1058
+  // Next ID: 1059
   extend TrackEvent {
     optional ChromeAppState chrome_app_state = 1000;
 
@@ -1606,5 +1622,7 @@
     optional PageLoad page_load = 1056;
 
     optional StartUp startup = 1057;
+
+    optional WebContentInteraction web_content_interaction = 1058;
   }
 }
diff --git a/base/types/expected.h b/base/types/expected.h
index 3cc205b6..1194a5b 100644
--- a/base/types/expected.h
+++ b/base/types/expected.h
@@ -5,6 +5,7 @@
 #ifndef BASE_TYPES_EXPECTED_H_
 #define BASE_TYPES_EXPECTED_H_
 
+#include <concepts>
 #include <type_traits>
 #include <utility>
 
@@ -133,9 +134,10 @@
 //     return base::ok(std::move(result));
 //   }
 template <typename T>
-class ok<T, /* is_void_v<T> = */ false> final {
+class ok final {
  public:
-  template <typename U = T, internal::EnableIfOkValueConstruction<T, U> = 0>
+  template <typename U = T>
+    requires(internal::IsOkValueConstruction<T, U>)
   constexpr explicit ok(U&& val) noexcept : value_(std::forward<U>(val)) {}
 
   template <typename... Args>
@@ -169,7 +171,8 @@
 };
 
 template <typename T>
-class ok<T, /* is_void_v<T> = */ true> final {
+  requires(std::is_void_v<T>)
+class ok<T> final {
  public:
   constexpr explicit ok() noexcept = default;
 
@@ -203,8 +206,8 @@
 class unexpected final {
  public:
   // [expected.un.ctor] Constructors
-  template <typename Err = E,
-            internal::EnableIfUnexpectedValueConstruction<E, Err> = 0>
+  template <typename Err = E>
+    requires(internal::IsUnexpectedValueConstruction<E, Err>)
   constexpr explicit unexpected(Err&& err) noexcept
       : error_(std::forward<Err>(err)) {}
 
@@ -266,7 +269,7 @@
 // [expected.expected], class template expected
 // https://eel.is/c++draft/expected#expected
 template <typename T, typename E>
-class [[nodiscard]] expected<T, E, /* is_void_v<T> = */ false> final {
+class [[nodiscard]] expected final {
   // Note: A partial specialization for void value types follows below.
   static_assert(!std::is_void_v<T>, "Error: T must not be void");
 
@@ -280,7 +283,7 @@
   template <typename U>
   using rebind = expected<U, E>;
 
-  template <typename U, typename G, bool IsVoid>
+  template <typename U, typename G>
   friend class expected;
 
   // [expected.object.ctor], constructors
@@ -289,28 +292,24 @@
   // Converting copy and move constructors. These constructors are explicit if
   // either the value or error type is not implicitly convertible from `rhs`'s
   // corresponding type.
-  template <typename U,
-            typename G,
-            internal::EnableIfExplicitConversion<T, E, const U&, const G&> = 0>
+  template <typename U, typename G>
+    requires(internal::IsExplicitConversion<T, E, const U&, const G&>)
   explicit constexpr expected(const expected<U, G>& rhs) noexcept
       : impl_(rhs.impl_) {}
 
-  template <typename U,
-            typename G,
-            internal::EnableIfImplicitConversion<T, E, const U&, const G&> = 0>
+  template <typename U, typename G>
+    requires(internal::IsImplicitConversion<T, E, const U&, const G&>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   /* implicit */ constexpr expected(const expected<U, G>& rhs) noexcept
       : impl_(rhs.impl_) {}
 
-  template <typename U,
-            typename G,
-            internal::EnableIfExplicitConversion<T, E, U, G> = 0>
+  template <typename U, typename G>
+    requires(internal::IsExplicitConversion<T, E, U, G>)
   explicit constexpr expected(expected<U, G>&& rhs) noexcept
       : impl_(std::move(rhs.impl_)) {}
 
-  template <typename U,
-            typename G,
-            internal::EnableIfImplicitConversion<T, E, U, G> = 0>
+  template <typename U, typename G>
+    requires(internal::IsImplicitConversion<T, E, U, G>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   /* implicit */ constexpr expected(expected<U, G>&& rhs) noexcept
       : impl_(std::move(rhs.impl_)) {}
@@ -318,49 +317,57 @@
   // Deviation from the Standard, which allows implicit conversions as long as U
   // is implicitly convertible to T: Chromium additionally requires that U is
   // not implicitly convertible to E.
-  template <typename U = T,
-            internal::EnableIfExplicitValueConstruction<T, E, U> = 0>
+  template <typename U = T>
+    requires(internal::IsExplicitValueConstruction<T, E, U>)
   explicit constexpr expected(U&& v) noexcept
       : impl_(kValTag, std::forward<U>(v)) {}
 
-  template <typename U = T,
-            internal::EnableIfImplicitValueConstruction<T, E, U> = 0>
+  template <typename U = T>
+    requires(internal::IsImplicitValueConstruction<T, E, U>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   /* implicit */ constexpr expected(U&& v) noexcept
       : impl_(kValTag, std::forward<U>(v)) {}
 
-  template <typename U, internal::EnableIfExplicitConstruction<T, const U&> = 0>
+  template <typename U>
+    requires(internal::IsExplicitConstruction<T, const U&>)
   explicit constexpr expected(const ok<U>& o) noexcept
       : impl_(kValTag, o.value()) {}
 
-  template <typename U, internal::EnableIfImplicitConstruction<T, const U&> = 0>
+  template <typename U>
+    requires(internal::IsImplicitConstruction<T, const U&>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   /* implicit */ constexpr expected(const ok<U>& o) noexcept
       : impl_(kValTag, o.value()) {}
 
-  template <typename U, internal::EnableIfExplicitConstruction<T, U> = 0>
+  template <typename U>
+    requires(internal::IsExplicitConstruction<T, U>)
   explicit constexpr expected(ok<U>&& o) noexcept
       : impl_(kValTag, std::move(o.value())) {}
 
-  template <typename U, internal::EnableIfImplicitConstruction<T, U> = 0>
+  template <typename U>
+    requires(internal::IsImplicitConstruction<T, U>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   /* implicit */ constexpr expected(ok<U>&& o) noexcept
       : impl_(kValTag, std::move(o.value())) {}
 
-  template <typename G, internal::EnableIfExplicitConstruction<E, const G&> = 0>
+  template <typename G>
+    requires(internal::IsExplicitConstruction<E, const G&>)
   explicit constexpr expected(const unexpected<G>& e) noexcept
       : impl_(kErrTag, e.error()) {}
 
-  template <typename G, internal::EnableIfImplicitConstruction<E, const G&> = 0>
+  template <typename G>
+    requires(internal::IsImplicitConstruction<E, const G&>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   /* implicit */ constexpr expected(const unexpected<G>& e) noexcept
       : impl_(kErrTag, e.error()) {}
 
-  template <typename G, internal::EnableIfExplicitConstruction<E, G> = 0>
+  template <typename G>
+    requires(internal::IsExplicitConstruction<E, G>)
   explicit constexpr expected(unexpected<G>&& e) noexcept
       : impl_(kErrTag, std::move(e.error())) {}
 
-  template <typename G, internal::EnableIfImplicitConstruction<E, G> = 0>
+  template <typename G>
+    requires(internal::IsImplicitConstruction<E, G>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   /* implicit */ constexpr expected(unexpected<G>&& e) noexcept
       : impl_(kErrTag, std::move(e.error())) {}
@@ -386,7 +393,8 @@
       : impl_(kErrTag, il, std::forward<Args>(args)...) {}
 
   // [expected.object.assign], assignment
-  template <typename U = T, internal::EnableIfValueAssignment<T, E, U> = 0>
+  template <typename U = T>
+    requires(internal::IsValueAssignment<T, E, U>)
   constexpr expected& operator=(U&& v) noexcept {
     emplace(std::forward<U>(v));
     return *this;
@@ -513,30 +521,26 @@
   //
   // `and_then` is overloaded for all possible forms of const and ref
   // qualifiers.
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfCopyConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::copy_constructible<E>)
   constexpr auto and_then(F&& f) & noexcept {
     return internal::AndThen(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfCopyConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::copy_constructible<E>)
   constexpr auto and_then(F&& f) const& noexcept {
     return internal::AndThen(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfMoveConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::move_constructible<E>)
   constexpr auto and_then(F&& f) && noexcept {
     return internal::AndThen(std::move(*this), std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfMoveConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::move_constructible<E>)
   constexpr auto and_then(F&& f) const&& noexcept {
     return internal::AndThen(std::move(*this), std::forward<F>(f));
   }
@@ -553,41 +557,30 @@
   //
   // `or_else` is overloaded for all possible forms of const and ref
   // qualifiers.
-  template <typename F,
-            typename LazyT = T,
-            internal::EnableIfCopyConstructible<LazyT> = 0>
+  template <typename F>
+    requires(std::copy_constructible<T>)
   constexpr auto or_else(F&& f) & noexcept {
     return internal::OrElse(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyT = T,
-            internal::EnableIfCopyConstructible<LazyT> = 0>
+  template <typename F>
+    requires(std::copy_constructible<T>)
   constexpr auto or_else(F&& f) const& noexcept {
     return internal::OrElse(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyT = T,
-            internal::EnableIfMoveConstructible<LazyT> = 0>
+  template <typename F>
+    requires(std::move_constructible<T>)
   constexpr auto or_else(F&& f) && noexcept {
     return internal::OrElse(std::move(*this), std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyT = T,
-            internal::EnableIfMoveConstructible<LazyT> = 0>
+  template <typename F>
+    requires(std::move_constructible<T>)
   constexpr auto or_else(F&& f) const&& noexcept {
     return internal::OrElse(std::move(*this), std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfCopyConstructible<LazyE> = 0>
-  constexpr auto transform(F&& f) & noexcept {
-    return internal::Transform(*this, std::forward<F>(f));
-  }
-
   // `transform`: This methods accepts a callable `f` that is invoked with
   // `value()` in case `has_value()` is true.
   //
@@ -602,23 +595,26 @@
   //
   // `transform` is overloaded for all possible forms of const and ref
   // qualifiers.
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfCopyConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::copy_constructible<E>)
+  constexpr auto transform(F&& f) & noexcept {
+    return internal::Transform(*this, std::forward<F>(f));
+  }
+
+  template <typename F>
+    requires(std::copy_constructible<E>)
   constexpr auto transform(F&& f) const& noexcept {
     return internal::Transform(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfMoveConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::move_constructible<E>)
   constexpr auto transform(F&& f) && noexcept {
     return internal::Transform(std::move(*this), std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfMoveConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::move_constructible<E>)
   constexpr auto transform(F&& f) const&& noexcept {
     return internal::Transform(std::move(*this), std::forward<F>(f));
   }
@@ -637,30 +633,26 @@
   //
   // `transform_error` is overloaded for all possible forms of const and ref
   // qualifiers.
-  template <typename F,
-            typename LazyT = T,
-            internal::EnableIfCopyConstructible<LazyT> = 0>
+  template <typename F>
+    requires(std::copy_constructible<T>)
   constexpr auto transform_error(F&& f) & noexcept {
     return internal::TransformError(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyT = T,
-            internal::EnableIfCopyConstructible<LazyT> = 0>
+  template <typename F>
+    requires(std::copy_constructible<T>)
   constexpr auto transform_error(F&& f) const& noexcept {
     return internal::TransformError(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyT = T,
-            internal::EnableIfMoveConstructible<LazyT> = 0>
+  template <typename F>
+    requires(std::move_constructible<T>)
   constexpr auto transform_error(F&& f) && noexcept {
     return internal::TransformError(std::move(*this), std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyT = T,
-            internal::EnableIfMoveConstructible<LazyT> = 0>
+  template <typename F>
+    requires(std::move_constructible<T>)
   constexpr auto transform_error(F&& f) const&& noexcept {
     return internal::TransformError(std::move(*this), std::forward<F>(f));
   }
@@ -685,7 +677,8 @@
 
 // [expected.void], partial specialization of expected for void types
 template <typename T, typename E>
-class [[nodiscard]] expected<T, E, /* is_void_v<T> = */ true> final {
+  requires(std::is_void_v<T>)
+class [[nodiscard]] expected<T, E> final {
   // Note: A partial specialization for non-void value types can be found above.
   static_assert(std::is_void_v<T>, "Error: T must be void");
 
@@ -699,7 +692,7 @@
   template <typename U>
   using rebind = expected<U, E>;
 
-  template <typename U, typename G, bool IsVoid>
+  template <typename U, typename G>
   friend class expected;
 
   // [expected.void.ctor], constructors
@@ -707,28 +700,24 @@
 
   // Converting copy and move constructors. These constructors are explicit if
   // the error type is not implicitly convertible from `rhs`'s error type.
-  template <typename U,
-            typename G,
-            internal::EnableIfExplicitVoidConversion<E, U, const G&> = 0>
+  template <typename U, typename G>
+    requires(internal::IsExplicitVoidConversion<E, U, const G&>)
   constexpr explicit expected(const expected<U, G>& rhs) noexcept
       : impl_(rhs.impl_) {}
 
-  template <typename U,
-            typename G,
-            internal::EnableIfImplicitVoidConversion<E, U, const G&> = 0>
+  template <typename U, typename G>
+    requires(internal::IsImplicitVoidConversion<E, U, const G&>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr /* implicit */ expected(const expected<U, G>& rhs) noexcept
       : impl_(rhs.impl_) {}
 
-  template <typename U,
-            typename G,
-            internal::EnableIfExplicitVoidConversion<E, U, G> = 0>
+  template <typename U, typename G>
+    requires(internal::IsExplicitVoidConversion<E, U, G>)
   constexpr explicit expected(expected<U, G>&& rhs) noexcept
       : impl_(std::move(rhs.impl_)) {}
 
-  template <typename U,
-            typename G,
-            internal::EnableIfImplicitVoidConversion<E, U, G> = 0>
+  template <typename U, typename G>
+    requires(internal::IsImplicitVoidConversion<E, U, G>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr /* implicit */ expected(expected<U, G>&& rhs) noexcept
       : impl_(std::move(rhs.impl_)) {}
@@ -736,20 +725,24 @@
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr /* implicit */ expected(base::ok<T>) noexcept {}
 
-  template <typename G, internal::EnableIfExplicitConstruction<E, const G&> = 0>
+  template <typename G>
+    requires(internal::IsExplicitConstruction<E, const G&>)
   explicit constexpr expected(const unexpected<G>& e) noexcept
       : impl_(kErrTag, e.error()) {}
 
-  template <typename G, internal::EnableIfImplicitConstruction<E, const G&> = 0>
+  template <typename G>
+    requires(internal::IsImplicitConstruction<E, const G&>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   /* implicit */ constexpr expected(const unexpected<G>& e) noexcept
       : impl_(kErrTag, e.error()) {}
 
-  template <typename G, internal::EnableIfExplicitConstruction<E, G> = 0>
+  template <typename G>
+    requires(internal::IsExplicitConstruction<E, G>)
   explicit constexpr expected(unexpected<G>&& e) noexcept
       : impl_(kErrTag, std::move(e.error())) {}
 
-  template <typename G, internal::EnableIfImplicitConstruction<E, G> = 0>
+  template <typename G>
+    requires(internal::IsImplicitConstruction<E, G>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   /* implicit */ constexpr expected(unexpected<G>&& e) noexcept
       : impl_(kErrTag, std::move(e.error())) {}
@@ -836,30 +829,26 @@
   //
   // `and_then` is overloaded for all possible forms of const and ref
   // qualifiers.
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfCopyConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::copy_constructible<E>)
   constexpr auto and_then(F&& f) & noexcept {
     return internal::AndThen(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfCopyConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::copy_constructible<E>)
   constexpr auto and_then(F&& f) const& noexcept {
     return internal::AndThen(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfMoveConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::move_constructible<E>)
   constexpr auto and_then(F&& f) && noexcept {
     return internal::AndThen(std::move(*this), std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfMoveConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::move_constructible<E>)
   constexpr auto and_then(F&& f) const&& noexcept {
     return internal::AndThen(std::move(*this), std::forward<F>(f));
   }
@@ -909,30 +898,26 @@
   //
   // `transform` is overloaded for all possible forms of const and ref
   // qualifiers.
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfCopyConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::copy_constructible<E>)
   constexpr auto transform(F&& f) & noexcept {
     return internal::Transform(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfCopyConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::copy_constructible<E>)
   constexpr auto transform(F&& f) const& noexcept {
     return internal::Transform(*this, std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfMoveConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::move_constructible<E>)
   constexpr auto transform(F&& f) && noexcept {
     return internal::Transform(std::move(*this), std::forward<F>(f));
   }
 
-  template <typename F,
-            typename LazyE = E,
-            internal::EnableIfMoveConstructible<LazyE> = 0>
+  template <typename F>
+    requires(std::move_constructible<E>)
   constexpr auto transform(F&& f) const&& noexcept {
     return internal::Transform(std::move(*this), std::forward<F>(f));
   }
@@ -991,28 +976,33 @@
 
 // [expected.object.eq], equality operators
 // [expected.void.eq], equality operators
-template <typename T, typename E, typename U, typename G, bool IsVoid>
-constexpr bool operator==(const expected<T, E, IsVoid>& x,
-                          const expected<U, G, IsVoid>& y) noexcept {
-  auto equal_values = [](const auto& x, const auto& y) {
+template <typename T, typename E, typename U, typename G>
+constexpr bool operator==(const expected<T, E>& x,
+                          const expected<U, G>& y) noexcept {
+  if (x.has_value() != y.has_value()) {
+    return false;
+  }
+
+  if (x.has_value()) {
     // Values for expected void types always compare equal.
-    if constexpr (IsVoid) {
+    if constexpr (std::is_void_v<T> && std::is_void_v<U>) {
       return true;
     } else {
       return x.value() == y.value();
     }
-  };
+  }
 
-  return x.has_value() == y.has_value() &&
-         (x.has_value() ? equal_values(x, y) : x.error() == y.error());
+  return x.error() == y.error();
 }
 
-template <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>
+template <typename T, typename E, typename U>
+  requires(!std::is_void_v<T>)
 constexpr bool operator==(const expected<T, E>& x, const U& v) noexcept {
   return x.has_value() && x.value() == v;
 }
 
-template <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>
+template <typename T, typename E, typename U>
+  requires(!std::is_void_v<T>)
 constexpr bool operator==(const U& v, const expected<T, E>& x) noexcept {
   return x == v;
 }
@@ -1045,18 +1035,20 @@
   return x == e;
 }
 
-template <typename T, typename E, typename U, typename G, bool IsVoid>
-constexpr bool operator!=(const expected<T, E, IsVoid>& x,
-                          const expected<U, G, IsVoid>& y) noexcept {
+template <typename T, typename E, typename U, typename G>
+constexpr bool operator!=(const expected<T, E>& x,
+                          const expected<U, G>& y) noexcept {
   return !(x == y);
 }
 
-template <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>
+template <typename T, typename E, typename U>
+  requires(!std::is_void_v<T>)
 constexpr bool operator!=(const expected<T, E>& x, const U& v) noexcept {
   return !(x == v);
 }
 
-template <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>
+template <typename T, typename E, typename U>
+  requires(!std::is_void_v<T>)
 constexpr bool operator!=(const U& v, const expected<T, E>& x) noexcept {
   return !(v == x);
 }
diff --git a/base/types/expected_internal.h b/base/types/expected_internal.h
index 1dc21f18..67ae16f 100644
--- a/base/types/expected_internal.h
+++ b/base/types/expected_internal.h
@@ -6,6 +6,7 @@
 #define BASE_TYPES_EXPECTED_INTERNAL_H_
 
 // IWYU pragma: private, include "base/types/expected.h"
+#include <concepts>
 #include <type_traits>
 #include <utility>
 
@@ -18,7 +19,7 @@
 // base::expected.
 namespace base {
 
-template <typename T, bool = std::is_void_v<T>>
+template <typename T>
 class ok;
 
 template <typename E>
@@ -31,38 +32,38 @@
 // in-place construction of unexpected values
 inline constexpr unexpect_t unexpect{};
 
-template <typename T, typename E, bool = std::is_void_v<T>>
+template <typename T, typename E>
 class expected;
 
 namespace internal {
 
 template <typename T>
-constexpr bool UnderlyingIsOk = false;
+inline constexpr bool UnderlyingIsOk = false;
 template <typename T>
-constexpr bool UnderlyingIsOk<ok<T>> = true;
+inline constexpr bool UnderlyingIsOk<ok<T>> = true;
 template <typename T>
-constexpr bool IsOk = UnderlyingIsOk<remove_cvref_t<T>>;
+inline constexpr bool IsOk = UnderlyingIsOk<remove_cvref_t<T>>;
 
 template <typename T>
-constexpr bool UnderlyingIsUnexpected = false;
+inline constexpr bool UnderlyingIsUnexpected = false;
 template <typename E>
-constexpr bool UnderlyingIsUnexpected<unexpected<E>> = true;
+inline constexpr bool UnderlyingIsUnexpected<unexpected<E>> = true;
 template <typename T>
-constexpr bool IsUnexpected = UnderlyingIsUnexpected<remove_cvref_t<T>>;
+inline constexpr bool IsUnexpected = UnderlyingIsUnexpected<remove_cvref_t<T>>;
 
 template <typename T>
-constexpr bool UnderlyingIsExpected = false;
+inline constexpr bool UnderlyingIsExpected = false;
 template <typename T, typename E>
-constexpr bool UnderlyingIsExpected<expected<T, E>> = true;
+inline constexpr bool UnderlyingIsExpected<expected<T, E>> = true;
 template <typename T>
-constexpr bool IsExpected = UnderlyingIsExpected<remove_cvref_t<T>>;
+inline constexpr bool IsExpected = UnderlyingIsExpected<remove_cvref_t<T>>;
 
 template <typename T, typename U>
-constexpr bool IsConstructibleOrConvertible =
+inline constexpr bool IsConstructibleOrConvertible =
     std::is_constructible_v<T, U> || std::is_convertible_v<U, T>;
 
 template <typename T, typename U>
-constexpr bool IsAnyConstructibleOrConvertible =
+inline constexpr bool IsAnyConstructibleOrConvertible =
     IsConstructibleOrConvertible<T, U&> ||
     IsConstructibleOrConvertible<T, U&&> ||
     IsConstructibleOrConvertible<T, const U&> ||
@@ -79,7 +80,7 @@
           typename UF,
           typename GF,
           typename ExUG = expected<remove_cvref_t<UF>, remove_cvref_t<GF>>>
-constexpr bool IsValidConversion =
+inline constexpr bool IsValidConversion =
     std::is_constructible_v<T, UF> && std::is_constructible_v<E, GF> &&
     !IsAnyConstructibleOrConvertible<T, ExUG> &&
     !IsAnyConstructibleOrConvertible<unexpected<E>, ExUG>;
@@ -94,108 +95,75 @@
           typename U,
           typename GF,
           typename ExUG = expected<U, remove_cvref_t<GF>>>
-constexpr bool IsValidVoidConversion =
+inline constexpr bool IsValidVoidConversion =
     std::is_void_v<U> && std::is_constructible_v<E, GF> &&
     !IsAnyConstructibleOrConvertible<unexpected<E>, ExUG>;
 
 // Checks whether expected<T, E> can be constructed from a value of type U.
 template <typename T, typename E, typename U>
-constexpr bool IsValidValueConstruction =
+inline constexpr bool IsValidValueConstruction =
     std::is_constructible_v<T, U> &&
     !std::is_same_v<remove_cvref_t<U>, absl::in_place_t> &&
     !std::is_same_v<remove_cvref_t<U>, expected<T, E>> && !IsOk<U> &&
     !IsUnexpected<U>;
 
 template <typename T, typename E, typename UF, typename GF>
-constexpr bool AreValueAndErrorConvertible =
+inline constexpr bool AreValueAndErrorConvertible =
     std::is_convertible_v<UF, T> && std::is_convertible_v<GF, E>;
 
-template <typename T>
-using EnableIfDefaultConstruction =
-    std::enable_if_t<std::is_default_constructible_v<T>, int>;
+template <typename T, typename E, typename UF, typename GF>
+inline constexpr bool IsExplicitConversion =
+    IsValidConversion<T, E, UF, GF> &&
+    !AreValueAndErrorConvertible<T, E, UF, GF>;
 
 template <typename T, typename E, typename UF, typename GF>
-using EnableIfExplicitConversion =
-    std::enable_if_t<IsValidConversion<T, E, UF, GF> &&
-                         !AreValueAndErrorConvertible<T, E, UF, GF>,
-                     int>;
-
-template <typename T, typename E, typename UF, typename GF>
-using EnableIfImplicitConversion =
-    std::enable_if_t<IsValidConversion<T, E, UF, GF> &&
-                         AreValueAndErrorConvertible<T, E, UF, GF>,
-                     int>;
+inline constexpr bool IsImplicitConversion =
+    IsValidConversion<T, E, UF, GF> &&
+    AreValueAndErrorConvertible<T, E, UF, GF>;
 
 template <typename E, typename U, typename GF>
-using EnableIfExplicitVoidConversion =
-    std::enable_if_t<IsValidVoidConversion<E, U, GF> &&
-                         !std::is_convertible_v<GF, E>,
-                     int>;
+inline constexpr bool IsExplicitVoidConversion =
+    IsValidVoidConversion<E, U, GF> && !std::is_convertible_v<GF, E>;
 
 template <typename E, typename U, typename GF>
-using EnableIfImplicitVoidConversion =
-    std::enable_if_t<IsValidVoidConversion<E, U, GF> &&
-                         std::is_convertible_v<GF, E>,
-                     int>;
+inline constexpr bool IsImplicitVoidConversion =
+    IsValidVoidConversion<E, U, GF> && std::is_convertible_v<GF, E>;
 
 template <typename T, typename U>
-using EnableIfOkValueConstruction =
-    std::enable_if_t<!std::is_same_v<remove_cvref_t<U>, ok<T>> &&
-                         !std::is_same_v<remove_cvref_t<U>, absl::in_place_t> &&
-                         std::is_constructible_v<T, U>,
-                     int>;
+inline constexpr bool IsOkValueConstruction =
+    !std::is_same_v<remove_cvref_t<U>, ok<T>> &&
+    !std::is_same_v<remove_cvref_t<U>, absl::in_place_t> &&
+    std::is_constructible_v<T, U>;
 
 template <typename T, typename U>
-using EnableIfUnexpectedValueConstruction =
-    std::enable_if_t<!std::is_same_v<remove_cvref_t<U>, unexpected<T>> &&
-                         !std::is_same_v<remove_cvref_t<U>, absl::in_place_t> &&
-                         std::is_constructible_v<T, U>,
-                     int>;
+inline constexpr bool IsUnexpectedValueConstruction =
+    !std::is_same_v<remove_cvref_t<U>, unexpected<T>> &&
+    !std::is_same_v<remove_cvref_t<U>, absl::in_place_t> &&
+    std::is_constructible_v<T, U>;
 
 template <typename T, typename E, typename U>
-using EnableIfExplicitValueConstruction =
-    std::enable_if_t<IsValidValueConstruction<T, E, U> &&
-                         (!std::is_convertible_v<U, T> ||
-                          std::is_convertible_v<U, E>),
-                     int>;
+inline constexpr bool IsExplicitValueConstruction =
+    IsValidValueConstruction<T, E, U> &&
+    (!std::is_convertible_v<U, T> || std::is_convertible_v<U, E>);
 
 template <typename T, typename E, typename U>
-using EnableIfImplicitValueConstruction =
-    std::enable_if_t<IsValidValueConstruction<T, E, U> &&
-                         std::is_convertible_v<U, T> &&
-                         !std::is_convertible_v<U, E>,
-                     int>;
+inline constexpr bool IsImplicitValueConstruction =
+    IsValidValueConstruction<T, E, U> && std::is_convertible_v<U, T> &&
+    !std::is_convertible_v<U, E>;
 
 template <typename T, typename U>
-using EnableIfExplicitConstruction =
-    std::enable_if_t<std::is_constructible_v<T, U> &&
-                         !std::is_convertible_v<U, T>,
-                     int>;
+inline constexpr bool IsExplicitConstruction =
+    std::is_constructible_v<T, U> && !std::is_convertible_v<U, T>;
 
 template <typename T, typename U>
-using EnableIfImplicitConstruction =
-    std::enable_if_t<std::is_constructible_v<T, U> &&
-                         std::is_convertible_v<U, T>,
-                     int>;
+inline constexpr bool IsImplicitConstruction =
+    std::is_constructible_v<T, U> && std::is_convertible_v<U, T>;
 
 template <typename T, typename E, typename U>
-using EnableIfValueAssignment =
-    std::enable_if_t<!std::is_same_v<expected<T, E>, remove_cvref_t<U>> &&
-                         !IsOk<U> && !IsUnexpected<U> &&
-                         std::is_constructible_v<T, U> &&
-                         std::is_assignable_v<T&, U>,
-                     int>;
-
-template <typename T>
-using EnableIfCopyConstructible =
-    std::enable_if_t<std::is_copy_constructible_v<T>, int>;
-
-template <typename T>
-using EnableIfMoveConstructible =
-    std::enable_if_t<std::is_move_constructible_v<T>, int>;
-
-template <typename T>
-using EnableIfNotVoid = std::enable_if_t<std::negation_v<std::is_void<T>>, int>;
+inline constexpr bool IsValueAssignment =
+    !std::is_same_v<expected<T, E>, remove_cvref_t<U>> && !IsOk<U> &&
+    !IsUnexpected<U> && std::is_constructible_v<T, U> &&
+    std::is_assignable_v<T&, U>;
 
 template <typename T, typename E>
 class ExpectedImpl {
@@ -208,8 +176,9 @@
   template <typename U, typename G>
   friend class ExpectedImpl;
 
-  template <typename LazyT = T, EnableIfDefaultConstruction<LazyT> = 0>
-  constexpr ExpectedImpl() noexcept : data_(kValTag) {}
+  constexpr ExpectedImpl() noexcept
+    requires(std::default_initializable<T>)
+      : data_(kValTag) {}
   constexpr ExpectedImpl(const ExpectedImpl& rhs) noexcept : data_(rhs.data_) {
     CHECK(!rhs.is_moved_from());
   }
diff --git a/base/types/optional_ref.h b/base/types/optional_ref.h
index ce26f8a..2beee4e7 100644
--- a/base/types/optional_ref.h
+++ b/base/types/optional_ref.h
@@ -96,13 +96,14 @@
   // Note: when constructing from a const reference, `optional_ref`'s template
   // argument must be const-qualified as well.
   // Note 2: avoiding direct use of `T` prevents implicit conversions.
-  template <typename U,
-            typename = std::enable_if_t<std::is_const_v<T> && IsCompatibleV<U>>>
+  template <typename U>
+    requires(std::is_const_v<T> && IsCompatibleV<U>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr optional_ref(
       const absl::optional<U>& o ABSL_ATTRIBUTE_LIFETIME_BOUND)
       : ptr_(o ? &*o : nullptr) {}
-  template <typename U, typename = std::enable_if_t<IsCompatibleV<U>>>
+  template <typename U>
+    requires(IsCompatibleV<U>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr optional_ref(absl::optional<U>& o ABSL_ATTRIBUTE_LIFETIME_BOUND)
       : ptr_(o ? &*o : nullptr) {}
@@ -113,7 +114,8 @@
   // Note: when constructing from a const pointer, `optional_ref`'s template
   // argument must be const-qualified as well.
   // Note 2: avoiding direct use of `T` prevents implicit conversions.
-  template <typename U, typename = std::enable_if_t<IsCompatibleV<U>>>
+  template <typename U>
+    requires(IsCompatibleV<U>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr optional_ref(U* p ABSL_ATTRIBUTE_LIFETIME_BOUND) : ptr_(p) {}
 
@@ -123,11 +125,13 @@
   // Note: when constructing from a const reference, `optional_ref`'s template
   // argument must be const-qualified as well.
   // Note 2: avoiding direct use of `T` prevents implicit conversions.
-  template <typename U, typename = std::enable_if_t<IsCompatibleV<const U>>>
+  template <typename U>
+    requires(IsCompatibleV<const U>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr optional_ref(const U& r ABSL_ATTRIBUTE_LIFETIME_BOUND)
       : ptr_(std::addressof(r)) {}
-  template <typename U, typename = std::enable_if_t<IsCompatibleV<U>>>
+  template <typename U>
+    requires(IsCompatibleV<U>)
   // NOLINTNEXTLINE(google-explicit-constructor)
   constexpr optional_ref(U& r ABSL_ATTRIBUTE_LIFETIME_BOUND)
       : ptr_(std::addressof(r)) {}
@@ -139,9 +143,9 @@
 
   // Constructs a `optional_ref<const T>` from a `optional_ref<T>`. Conversions
   // in the reverse direction are disallowed.
-  template <typename U = T, typename = std::enable_if_t<std::is_const_v<U>>>
   // NOLINTNEXTLINE(google-explicit-constructor)
-  constexpr optional_ref(optional_ref<std::remove_const_t<U>> rhs)
+  constexpr optional_ref(optional_ref<std::remove_const_t<T>> rhs)
+    requires(std::is_const_v<T>)
       : ptr_(rhs.as_ptr()) {}
 
   // Copy construction is allowed to make it possible to pass `optional_ref`s to
@@ -178,8 +182,8 @@
   // Convenience method for turning a non-owning `optional_ref` into an owning
   // `absl::optional`. Incurs a copy; useful when saving an `optional_ref`
   // function parameter as a field, et cetera.
-  template <typename U = std::decay_t<T>,
-            typename = std::enable_if_t<std::is_constructible_v<U, T>>>
+  template <typename U = std::decay_t<T>>
+    requires(std::constructible_from<U, T>)
   constexpr absl::optional<U> CopyAsOptional() const {
     return ptr_ ? absl::optional<U>(*ptr_) : absl::nullopt;
   }
diff --git a/cc/input/input_handler.cc b/cc/input/input_handler.cc
index 2b1112b1..d893da3 100644
--- a/cc/input/input_handler.cc
+++ b/cc/input/input_handler.cc
@@ -184,6 +184,15 @@
     // still need to walk up the scroll tree looking for the first node that
     // can consume delta from the scroll state.
     scrolling_node = FindNodeToLatch(scroll_state, starting_node, type);
+
+    // When using fluent overlay scrollbars and a subscroller receives a scroll
+    // event, but the scroll chains up to a different node, we want to flash the
+    // scrollbars to show that the node is scrollable.
+    if (scrolling_node &&
+        compositor_delegate_->GetSettings().enable_fluent_overlay_scrollbar &&
+        scrolling_node->element_id != starting_node->element_id) {
+      compositor_delegate_->WillScrollContent(starting_node->element_id);
+    }
   }
 
   if (!scrolling_node) {
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc
index dd5409b..7bf17fd 100644
--- a/cc/layers/picture_layer_impl.cc
+++ b/cc/layers/picture_layer_impl.cc
@@ -468,11 +468,11 @@
           // The raster_contents_scale_ is the best scale that the layer is
           // trying to produce, even though it may not be ideal. Since that's
           // the best the layer can promise in the future, consider those as
-          // complete. But if a tile is ideal scale, we don't want to consider
-          // it incomplete and trying to replace it with a tile at a worse
-          // scale.
+          // complete. Also consider a tile complete if it is ideal scale or
+          // better. Note that PLTS::CoverageIterator prefers the _smallest_
+          // scale that is >= ideal, which may be < raster_contents_scale_.
           if (iter->contents_scale_key() != raster_contents_scale_key() &&
-              iter->contents_scale_key() != ideal_contents_scale_key() &&
+              iter->contents_scale_key() < ideal_contents_scale_key() &&
               geometry_rect.Intersects(scaled_viewport_for_tile_priority)) {
             append_quads_data->num_incomplete_tiles++;
           }
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 52431f1..883a41d3 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -13933,6 +13933,86 @@
     Range(0,
           FluentOverlayScrollbarOpacityLayerTreeHostImplTest::kParamSteps + 1));
 
+TEST_F(FluentOverlayScrollbarLayerTreeHostImplTest,
+       FluentScrollbarFlashAfterScrollUpdate) {
+  auto* root_scrollbar = CreateAndRegisterPaintedScrollbarLayer();
+  
+  // Register child scrollable area layer and scrollbar.
+  LayerImpl* root_scroll = OuterViewportScrollLayer();
+  gfx::Size child_layer_size(250, 150);
+  gfx::Size child_scrollbar_size(gfx::Size(15, child_layer_size.height()));
+  auto* child =
+      AddScrollableLayer(root_scroll, gfx::Size(100, 100), child_layer_size);
+  GetTransformNode(child)->post_translation = gfx::Vector2dF(50, 50);
+
+  auto* child_scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+      host_impl_->active_tree(), ScrollbarOrientation::kVertical, false, true);
+  // SetupScrollbarLayerCommon will register the scrollbar, which sets the
+  // layer's opacity to 0. An effect node for the scrollbar layer object needs
+  // to be registered in the EffectTree before this happens.
+  auto& effect_node =
+      CreateEffectNode(child_scrollbar, child->effect_tree_index());
+  SetupScrollbarLayerCommon(child, child_scrollbar);
+  // SetupScrollbarLayerCommon calls CopyProperties which overrides the effect
+  // tree node registered to the scrollbar layer. We need to reset it to the
+  // one we registered above.
+  child_scrollbar->SetEffectTreeIndex(effect_node.id);
+  child_scrollbar->SetHitTestOpaqueness(HitTestOpaqueness::kMixed);
+  child_scrollbar->SetBounds(child_scrollbar_size);
+
+  auto reset_scrollbars = [root_scrollbar, child_scrollbar](
+                              LayerTreeImpl* active_tree,
+                              base::OnceClosure& animation_task) {
+    GetEffectNode(root_scrollbar)->opacity = 0;
+    GetEffectNode(child_scrollbar)->opacity = 0;
+    UpdateDrawProperties(active_tree);
+    animation_task.Reset();
+  };
+
+  reset_scrollbars(host_impl_->active_tree(), animation_task_);
+  UpdateDrawProperties(host_impl_->active_tree());
+  host_impl_->active_tree()->UpdateScrollbarGeometries();
+  host_impl_->active_tree()->DidBecomeActive();
+
+  EXPECT_EQ(root_scrollbar->Opacity(), 0);
+  EXPECT_EQ(child_scrollbar->Opacity(), 0);
+
+  // Scroll on root should only flash root.
+  GetInputHandler().RootScrollBegin(
+      BeginState(gfx::Point(20, 20), gfx::Vector2dF(0, 10),
+                 ui::ScrollInputType::kWheel)
+          .get(),
+      ui::ScrollInputType::kWheel);
+  GetInputHandler().ScrollUpdate(UpdateState(gfx::Point(20, 20),
+                                             gfx::Vector2d(0, 10),
+                                             ui::ScrollInputType::kWheel)
+                                     .get());
+  GetInputHandler().ScrollEnd();
+
+  EXPECT_EQ(root_scrollbar->Opacity(), 1.f);
+  EXPECT_EQ(child_scrollbar->Opacity(), 0);
+  EXPECT_FALSE(animation_task_.is_null());
+
+  reset_scrollbars(host_impl_->active_tree(), animation_task_);
+
+  // Scrolling on child in a direction in which it can't scroll (upwards) should
+  // flash the hit tested scrollbar and the one that ends up receiving the
+  // scroll event (the root scrollbar in this case).
+  GetInputHandler().ScrollBegin(
+      BeginState(gfx::Point(70, 70), gfx::Vector2dF(0, -100),
+                 ui::ScrollInputType::kWheel)
+          .get(),
+      ui::ScrollInputType::kWheel);
+  GetInputHandler().ScrollUpdate(
+      AnimatedUpdateState(gfx::Point(70, 70), gfx::Vector2d(0, -100)).get());
+  GetInputHandler().ScrollEnd();
+
+  EXPECT_TRUE(root_scrollbar->Opacity());
+  EXPECT_TRUE(child_scrollbar->Opacity());
+
+  EXPECT_FALSE(animation_task_.is_null());
+}
+
 // Fluent Overlay Scrollbars opacity should be set to zero when creating
 // the animation controller.
 TEST_F(FluentOverlayScrollbarLayerTreeHostImplTest,
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 09d61f9..c45695d1 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -379,7 +379,6 @@
   "java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripStacker.java",
   "java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripTabHoverCardView.java",
   "java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSource.java",
-  "java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDropTarget.java",
   "java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabLoadTracker.java",
   "java/src/org/chromium/chrome/browser/compositor/resources/StaticResourcePreloads.java",
   "java/src/org/chromium/chrome/browser/compositor/resources/SystemResourcePreloads.java",
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 3837fb3..986bd799 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -125,7 +125,6 @@
   "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripStackerUnitTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripTabHoverCardViewUnitTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSourceTest.java",
-  "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDropTargetTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabUsageTrackerTest.java",
   "junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TestTabModel.java",
   "junit/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayerTest.java",
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
index e2292ac..ceadac8 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
@@ -55,6 +55,7 @@
     private final FeedSurfaceCoordinator mFeedSurfaceCoordinator;
     private final ExploreSurfaceNavigationDelegate mExploreSurfaceNavigationDelegate;
     private final boolean mIsPlaceholderShownInitially;
+    private final Profile mProfile;
 
     private long mContentFirstAvailableTimeMs;
     // Whether missing a histogram record when onOverviewShownAtLaunch() is called. It is possible
@@ -76,6 +77,7 @@
         mJankTracker = jankTracker;
         mExploreSurfaceNavigationDelegate = new ExploreSurfaceNavigationDelegate(parentTabSupplier);
         mIsPlaceholderShownInitially = isPlaceholderShown;
+        mProfile = profile;
 
         mFeedSurfaceCoordinator = new FeedSurfaceCoordinator(mActivity, snackbarManager,
                 windowAndroid, mJankTracker, /*snapScrollHelper=*/null, /*ntpHeader=*/null,
@@ -142,8 +144,14 @@
     private class ExploreSurfaceActionDelegate extends FeedActionDelegateImpl {
         ExploreSurfaceActionDelegate(SnackbarManager snackbarManager, BookmarkModel bookmarkModel,
                 TabModelSelector tabModelSelector) {
-            super(mActivity, snackbarManager, mExploreSurfaceNavigationDelegate, bookmarkModel,
-                    BrowserUiUtils.HostSurface.START_SURFACE, tabModelSelector);
+            super(
+                    mActivity,
+                    snackbarManager,
+                    mExploreSurfaceNavigationDelegate,
+                    bookmarkModel,
+                    BrowserUiUtils.HostSurface.START_SURFACE,
+                    tabModelSelector,
+                    mProfile);
         }
 
         @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 88676b8..78e1ef8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -2311,47 +2311,56 @@
     }
 
     private void logIntentInfo(Intent intent, int windowId) {
+        boolean willUseNewInstance = MultiWindowUtils.willUseNewInstance();
+        boolean isFromOs =
+                getReferrer() != null
+                        && getReferrer().toString().equals(SOURCE_ACTIVITY_REFERRER_OS);
+        boolean isFromChrome = IntentHandler.wasIntentSenderChrome(intent);
+
         var logMessage =
-                "Intent info:\nAction: "
+                "Intent action: "
                         + intent.getAction()
-                        + "\nContains LAUNCHER category: "
+                        + "\nIntent routed via ChromeLauncherActivity: "
+                        + IntentUtils.safeGetBooleanExtra(
+                                intent,
+                                IntentHandler.EXTRA_LAUNCHED_VIA_CHROME_LAUNCHER_ACTIVITY,
+                                false)
+                        + "\nIntent contains LAUNCHER category: "
                         + intent.hasCategory(Intent.CATEGORY_LAUNCHER)
-                        + "\nContains FLAG_ACTIVITY_MULTIPLE_TASK: "
+                        + "\nIntent contains FLAG_ACTIVITY_MULTIPLE_TASK: "
                         + ((intent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0)
-                        + "\nContains FLAG_ACTIVITY_NEW_TASK: "
+                        + "\nIntent contains FLAG_ACTIVITY_NEW_TASK: "
                         + ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0)
-                        + "\nComponent class name: "
+                        + "\nIntent component: "
                         + (intent.getComponent() == null
                                 ? "N/A"
                                 : intent.getComponent().getClassName())
-                        + "\nIs from self: "
-                        + IntentUtils.isTrustedIntentFromSelf(intent)
+                        + "\nIntent sent by OS: "
+                        + isFromOs
                         + "\n@ExternalAppId of intent source: "
-                        + IntentHandler.determineExternalIntentSource(intent);
+                        + IntentHandler.determineExternalIntentSource(intent)
+                        + "\nIs new instanceId allocated: "
+                        + willUseNewInstance;
         Log.i(TAG_MULTI_INSTANCE, logMessage);
         // Only crash-report if a valid window ID is allocated to launch the intent.
         if (windowId == INVALID_WINDOW_ID) return;
 
-        boolean willUseNewInstance = MultiWindowUtils.willUseNewInstance();
         // Report an exception iff all the following conditions are satisfied:
-        // 1. The intent is a VIEW or MAIN action intent.
-        // 2. The intent will be launched in a new instance of Chrome.
-        // 3. The intent is not sourced from Chrome or the OS.
-        // 4. The intent is not a CATEGORY_LAUNCHER intent.
-        // 5. The device is a phone.
-        if ((Intent.ACTION_VIEW.equals(intent.getAction())
-                        || Intent.ACTION_MAIN.equals(intent.getAction()))
-                && willUseNewInstance
-                && (!IntentUtils.isTrustedIntentFromSelf(intent)
-                        && (getReferrer() == null
-                                || !getReferrer().toString().equals(SOURCE_ACTIVITY_REFERRER_OS)))
-                && !intent.hasCategory(Intent.CATEGORY_LAUNCHER)
-                && !DeviceFormFactor.isNonMultiDisplayContextOnTablet(this)) {
+        // 1. The intent will be launched in a new instance of Chrome.
+        // 2. The device is a phone.
+        // 3. The intent is a VIEW intent with a non-Chrome source, OR, a MAIN intent with a
+        // non-Chrome / non-OS source.
+        boolean isViewIntent = Intent.ACTION_VIEW.equals(intent.getAction()) && !isFromChrome;
+        boolean isMainIntent =
+                Intent.ACTION_MAIN.equals(intent.getAction()) && !isFromChrome && !isFromOs;
+
+        if (willUseNewInstance
+                && !DeviceFormFactor.isNonMultiDisplayContextOnTablet(this)
+                && (isViewIntent || isMainIntent)) {
             logMessage =
                     "This is not a crash. Logging info for intent received in ChromeTabbedActivity"
                             + " dispatched via AsyncInitializationActivity#onCreate() that could"
-                            + " potentially create a new Chrome instance, for investigation of"
-                            + " crbug.com/1484026.\n"
+                            + " potentially create a new Chrome instance.\n"
                             + logMessage;
             ChromePureJavaExceptionReporter.reportJavaException(new Throwable(logMessage));
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index a2ce2cf1..409afe3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -258,6 +258,10 @@
      */
     public static final String EXTRA_TAB_INDEX = "com.android.chrome.tab_index";
 
+    /** A boolean to indicate whether an intent was launched via ChromeLauncherActivity. */
+    public static final String EXTRA_LAUNCHED_VIA_CHROME_LAUNCHER_ACTIVITY =
+            "org.chromium.chrome.browser.launched_via_chrome_launcher_activity";
+
     private static Pair<Integer, String> sPendingReferrer;
     private static int sReferrerId;
     private static String sPendingIncognitoUrl;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
index 468dd87d..86a97aa 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -429,6 +429,10 @@
                     "Android.Intent.HasNonSpoofablePackageName", hasNonSpoofablePackageName());
         }
 
+        if (mActivity instanceof ChromeLauncherActivity) {
+            newIntent.putExtra(IntentHandler.EXTRA_LAUNCHED_VIA_CHROME_LAUNCHER_ACTIVITY, true);
+        }
+
         Uri extraReferrer = mActivity.getReferrer();
         if (extraReferrer != null) {
             newIntent.putExtra(IntentHandler.EXTRA_ACTIVITY_REFERRER, extraReferrer.toString());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index 108e002..30a98cd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -2067,15 +2067,47 @@
         mActivityTabProvider.setLayoutStateProvider(layoutManager);
 
         if (mContextualSearchManagerSupplier.hasValue()) {
-            mContextualSearchManagerSupplier.get().initialize(contentContainer, layoutManager,
-                    mRootUiCoordinator.getBottomSheetController(), compositorViewHolder,
-                    getControlContainerHeightResource() == ActivityUtils.NO_RESOURCE_ID
-                            ? 0f
-                            : getResources().getDimension(getControlContainerHeightResource()),
-                    getToolbarManager(), getActivityType(), getIntentRequestTracker());
+            if (getProfileProviderSupplier().hasValue()) {
+                initializeContextualSearchManager(
+                        layoutManager,
+                        contentContainer,
+                        compositorViewHolder,
+                        getProfileProviderSupplier().get().getOriginalProfile());
+            } else {
+                getProfileProviderSupplier()
+                        .onAvailable(
+                                (profileProvider) -> {
+                                    initializeContextualSearchManager(
+                                            layoutManager,
+                                            contentContainer,
+                                            compositorViewHolder,
+                                            profileProvider.getOriginalProfile());
+                                });
+            }
         }
     }
 
+    private void initializeContextualSearchManager(
+            LayoutManagerImpl layoutManager,
+            ViewGroup contentContainer,
+            CompositorViewHolder compositorViewHolder,
+            Profile profile) {
+        mContextualSearchManagerSupplier
+                .get()
+                .initialize(
+                        contentContainer,
+                        profile,
+                        layoutManager,
+                        mRootUiCoordinator.getBottomSheetController(),
+                        compositorViewHolder,
+                        getControlContainerHeightResource() == ActivityUtils.NO_RESOURCE_ID
+                                ? 0f
+                                : getResources().getDimension(getControlContainerHeightResource()),
+                        getToolbarManager(),
+                        getActivityType(),
+                        getIntentRequestTracker());
+    }
+
     /**
      * @return An {@link ObservableSupplier} that will supply the {@link LayoutManagerImpl} when it
      *         is ready.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java
index 36bb525..0a830ae4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java
@@ -81,11 +81,17 @@
         // TODO(crbug/1399617) Eliminate code duplication with
         //     FeedActionDelegateImpl
         BookmarkModel bookmarkModel = BookmarkModel.getForProfile(mProfile);
-        bookmarkModel.finishLoadingBookmarkModel(() -> {
-            assert ThreadUtils.runningOnUiThread();
-            BookmarkUtils.addToReadingList(
-                    new GURL(url), title, mSnackbarManager, bookmarkModel, mActivityContext);
-        });
+        bookmarkModel.finishLoadingBookmarkModel(
+                () -> {
+                    assert ThreadUtils.runningOnUiThread();
+                    BookmarkUtils.addToReadingList(
+                            new GURL(url),
+                            title,
+                            mSnackbarManager,
+                            bookmarkModel,
+                            mActivityContext,
+                            mProfile);
+                });
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/feed/FeedActionDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/feed/FeedActionDelegateImpl.java
index 601bca4..e62eecd0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/feed/FeedActionDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/feed/FeedActionDelegateImpl.java
@@ -49,19 +49,26 @@
     private final Context mActivityContext;
     private final SnackbarManager mSnackbarManager;
     private final TabModelSelector mTabModelSelector;
+    private final Profile mProfile;
 
     @BrowserUiUtils.HostSurface
     private int mHostSurface;
 
-    public FeedActionDelegateImpl(Context activityContext, SnackbarManager snackbarManager,
-            NativePageNavigationDelegate navigationDelegate, BookmarkModel bookmarkModel,
-            @BrowserUiUtils.HostSurface int hostSurface, TabModelSelector tabModelSelector) {
+    public FeedActionDelegateImpl(
+            Context activityContext,
+            SnackbarManager snackbarManager,
+            NativePageNavigationDelegate navigationDelegate,
+            BookmarkModel bookmarkModel,
+            @BrowserUiUtils.HostSurface int hostSurface,
+            TabModelSelector tabModelSelector,
+            Profile profile) {
         mActivityContext = activityContext;
         mNavigationDelegate = navigationDelegate;
         mBookmarkModel = bookmarkModel;
         mSnackbarManager = snackbarManager;
         mHostSurface = hostSurface;
         mTabModelSelector = tabModelSelector;
+        mProfile = profile;
     }
     @Override
     public void downloadPage(String url) {
@@ -115,11 +122,17 @@
 
     @Override
     public void addToReadingList(String title, String url) {
-        mBookmarkModel.finishLoadingBookmarkModel(() -> {
-            assert ThreadUtils.runningOnUiThread();
-            BookmarkUtils.addToReadingList(
-                    new GURL(url), title, mSnackbarManager, mBookmarkModel, mActivityContext);
-        });
+        mBookmarkModel.finishLoadingBookmarkModel(
+                () -> {
+                    assert ThreadUtils.runningOnUiThread();
+                    BookmarkUtils.addToReadingList(
+                            new GURL(url),
+                            title,
+                            mSnackbarManager,
+                            mBookmarkModel,
+                            mActivityContext,
+                            mProfile);
+                });
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
index 17d4373..17f53473 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
@@ -130,7 +130,8 @@
                 fromExplicitTrackUi,
                 newBookmarkId,
                 /* wasBookmarkMoved= */ false,
-                /* isNewBookmark= */ true);
+                /* isNewBookmark= */ true,
+                tab.getProfile());
         callback.onResult(newBookmarkId);
     }
 
@@ -143,6 +144,7 @@
      * @param bookmarkId The BookmarkId to show the save flow for. Can be null in some cases.
      * @param wasBookmarkMoved Whether the save flow is shown as a reslult of a moved bookmark.
      * @param isNewBookmark Whether the bookmark is newly created.
+     * @param profile The profile currently used.
      */
     public static void showSaveFlow(
             @NonNull Activity activity,
@@ -150,13 +152,13 @@
             boolean fromExplicitTrackUi,
             @Nullable BookmarkId bookmarkId,
             boolean wasBookmarkMoved,
-            boolean isNewBookmark) {
+            boolean isNewBookmark,
+            @NonNull Profile profile) {
         if (bookmarkId == null) {
             Log.e(TAG, "Null bookmark found when showing the save flow, aborting.");
             return;
         }
 
-        Profile profile = Profile.getLastUsedRegularProfile();
         ShoppingService shoppingService = ShoppingServiceFactory.getForProfile(profile);
 
         BookmarkSaveFlowCoordinator bookmarkSaveFlowCoordinator =
@@ -180,7 +182,12 @@
             @BookmarkType int bookmarkType) {
         if (bookmarkType == BookmarkType.READING_LIST) {
             return addToReadingList(
-                    tab.getOriginalUrl(), tab.getTitle(), snackbarManager, bookmarkModel, activity);
+                    tab.getOriginalUrl(),
+                    tab.getTitle(),
+                    snackbarManager,
+                    bookmarkModel,
+                    activity,
+                    tab.getProfile());
         }
         BookmarkId bookmarkId =
                 addBookmarkInternal(
@@ -265,13 +272,15 @@
      * @param bookmarkBridge The bookmark bridge that talks to the bookmark backend.
      * @param context The associated context.
      * @return The bookmark ID created after saving the article to the reading list.
+     * @param profile The profile currently used.
      */
     public static BookmarkId addToReadingList(
             GURL url,
             String title,
             SnackbarManager snackbarManager,
             BookmarkBridge bookmarkBridge,
-            Context context) {
+            Context context,
+            @NonNull Profile profile) {
         assert bookmarkBridge.isBookmarkModelLoaded();
         BookmarkId bookmarkId = bookmarkBridge.addToReadingList(title, url);
 
@@ -284,7 +293,7 @@
                             Snackbar.UMA_READING_LIST_BOOKMARK_ADDED);
             snackbarManager.showSnackbar(snackbar);
 
-            TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile())
+            TrackerFactory.getTrackerForProfile(profile)
                     .notifyEvent(EventConstants.READ_LATER_ARTICLE_SAVED);
         }
         return bookmarkId;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java
index f1f921b..456d158 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java
@@ -88,13 +88,15 @@
         } else {
             // In the case where the bookmark exists, re-show the save flow with price-tracking
             // enabled.
+            assert currentTab != null : "currentTab cannot be null";
             BookmarkUtils.showSaveFlow(
                     mActivity,
                     mBottomSheetControllerSupplier.get(),
                     /* fromExplicitTrackUi= */ true,
                     bookmarkId,
                     /* wasBookmarkMoved= */ false,
-                    /* isNewBookmark= */ false);
+                    /* isNewBookmark= */ false,
+                    currentTab.getProfile());
         }
     }
 
@@ -128,7 +130,8 @@
                             mBottomSheetControllerSupplier.get(),
                             bookmarkModel,
                             bookmarkId,
-                            bookmarkType)) {
+                            bookmarkType,
+                            tabToBookmark.getProfile())) {
                         return;
                     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java
index c393160c..2b94e1a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java
@@ -34,6 +34,7 @@
 import org.chromium.chrome.browser.layouts.components.VirtualView;
 import org.chromium.chrome.browser.layouts.scene_layer.SceneOverlayLayer;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabBrowserControlsConstraintsHelper;
 import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.ScrollDirection;
@@ -126,6 +127,9 @@
     /** The {@link WindowAndroid} for the current activity.  */
     private final WindowAndroid mWindowAndroid;
 
+    /** The {@link Profile} associated with this Panel. */
+    private final Profile mProfile;
+
     /**
      * The {@link CompositorViewHolder}, used as an anchor view. Also injected into other classes.
      */
@@ -186,22 +190,29 @@
      * @param layoutManager A {@link LayoutManagerImpl} for observing changes in the active layout.
      * @param panelManager The {@link OverlayPanelManager} responsible for showing panels.
      * @param browserControlsStateProvider The {@link BrowserControlsStateProvider} for measuring
-     *         controls.
+     *     controls.
      * @param windowAndroid The {@link WindowAndroid} for the current activity.
+     * @param profile The Profile this OverlayPanel is associated with.
      * @param compositorViewHolder The {@link CompositorViewHolder}
      * @param toolbarHeightDp The height of the toolbar in dp.
      * @param currentTabSupplier Supplies the current {@link Tab}.
      */
-    public OverlayPanel(@NonNull Context context, @NonNull LayoutManagerImpl layoutManager,
+    public OverlayPanel(
+            @NonNull Context context,
+            @NonNull LayoutManagerImpl layoutManager,
             @NonNull OverlayPanelManager panelManager,
             @NonNull BrowserControlsStateProvider browserControlsStateProvider,
-            @NonNull WindowAndroid windowAndroid, @NonNull ViewGroup compositorViewHolder,
-            float toolbarHeightDp, @NonNull Supplier<Tab> currentTabSupplier) {
+            @NonNull WindowAndroid windowAndroid,
+            @NonNull Profile profile,
+            @NonNull ViewGroup compositorViewHolder,
+            float toolbarHeightDp,
+            @NonNull Supplier<Tab> currentTabSupplier) {
         super(context, layoutManager, toolbarHeightDp);
         mLayoutManager = layoutManager;
         mContentFactory = this;
         mBrowserControlsStateProvider = browserControlsStateProvider;
         mWindowAndroid = windowAndroid;
+        mProfile = profile;
         mCompositorViewHolder = compositorViewHolder;
         mCurrentTabSupplier = currentTabSupplier;
 
@@ -219,6 +230,29 @@
         mLayoutManager.addSceneChangeObserver(mSceneChangeObserver);
     }
 
+    /** Return the {@link WindowAndroid} this panel is associated with. */
+    protected WindowAndroid getWindowAndroid() {
+        return mWindowAndroid;
+    }
+
+    /** Return the {@link Profile} this panel is associated with. */
+    protected Profile getProfile() {
+        return mProfile;
+    }
+
+    /**
+     * Return the {@link ViewGroup} corresponding to the CompositorViewHolder of the Activity
+     * containing this OverlayPanel.
+     */
+    protected ViewGroup getCompositorViewHolder() {
+        return mCompositorViewHolder;
+    }
+
+    /** Return the {@link Tab} supplier for the Activity containing this OverlayPanel. */
+    protected Supplier<Tab> getCurrentTabSupplier() {
+        return mCurrentTabSupplier;
+    }
+
     /**
      * Destroy the native components associated with this panel's content.
      */
@@ -518,9 +552,15 @@
      */
     @Override
     public OverlayPanelContent createNewOverlayPanelContent() {
-        return new OverlayPanelContent(new OverlayContentDelegate(),
-                new OverlayContentProgressObserver(), mActivity, /* isIncognito= */ false,
-                getBarHeight(), mCompositorViewHolder, mWindowAndroid, mCurrentTabSupplier);
+        return new OverlayPanelContent(
+                new OverlayContentDelegate(),
+                new OverlayContentProgressObserver(),
+                mActivity,
+                mProfile,
+                getBarHeight(),
+                mCompositorViewHolder,
+                mWindowAndroid,
+                mCurrentTabSupplier);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java
index 807b097..355f1629 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java
@@ -22,7 +22,6 @@
 import org.chromium.chrome.browser.content.WebContentsFactory;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
 import org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl;
-import org.chromium.chrome.browser.incognito.IncognitoUtils;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
@@ -60,6 +59,9 @@
     /** Used for progress bar events. */
     private final WebContentsDelegateAndroid mWebContentsDelegate;
 
+    /** The Profile this OverlayPanel is associated with. */
+    private final Profile mProfile;
+
     /** The WebContents that this panel will display. */
     private WebContents mWebContents;
 
@@ -116,9 +118,6 @@
     // java layer. Otherwise, the instance could be garbage-collected unexpectedly.
     private InterceptNavigationDelegate mInterceptNavigationDelegate;
 
-    /** Set to {@code True} if opened for an incognito tab. */
-    private boolean mIsIncognito;
-
     /** The desired size of the {@link ContentView} associated with this panel content. */
     private int mContentViewWidth;
     private int mContentViewHeight;
@@ -193,24 +192,29 @@
 
     /**
      * @param contentDelegate An observer for events that occur on this content. If null is passed
-     *                        for this parameter, the default one will be used.
+     *     for this parameter, the default one will be used.
      * @param progressObserver An observer for progress related events.
      * @param activity The {@link Activity} that contains this object.
-     * @param isIncognito {@True} if opened for an incognito tab
+     * @param profile The Profile associated with the OverlayPanel.
      * @param barHeight The height of the bar at the top of the OverlayPanel in dp.
      * @param compositorViewHolder The {@link CompositorViewHolder} for the current activity.
      * @param windowAndroid The {@link WindowAndroid} for the current activity.
      * @param currentTabSupplier Supplies the current activity {@link Tab}.
      */
-    public OverlayPanelContent(@NonNull OverlayContentDelegate contentDelegate,
-            @NonNull OverlayContentProgressObserver progressObserver, @NonNull Activity activity,
-            boolean isIncognito, float barHeight, @NonNull ViewGroup compositorViewHolder,
-            @NonNull WindowAndroid windowAndroid, @NonNull Supplier<Tab> currentTabSupplier) {
+    public OverlayPanelContent(
+            @NonNull OverlayContentDelegate contentDelegate,
+            @NonNull OverlayContentProgressObserver progressObserver,
+            @NonNull Activity activity,
+            @NonNull Profile profile,
+            float barHeight,
+            @NonNull ViewGroup compositorViewHolder,
+            @NonNull WindowAndroid windowAndroid,
+            @NonNull Supplier<Tab> currentTabSupplier) {
         mNativeOverlayPanelContentPtr = OverlayPanelContentJni.get().init(OverlayPanelContent.this);
         mContentDelegate = contentDelegate;
         mProgressObserver = progressObserver;
         mActivity = activity;
-        mIsIncognito = isIncognito;
+        mProfile = profile;
         mBarHeightPx = (int) (barHeight * mActivity.getResources().getDisplayMetrics().density);
         mCompositorViewHolder = compositorViewHolder;
         mWindowAndroid = windowAndroid;
@@ -348,9 +352,8 @@
             destroyWebContents();
         }
 
-        Profile profile = IncognitoUtils.getProfileFromWindowAndroid(mWindowAndroid, mIsIncognito);
         // Creates an initially hidden WebContents which gets shown when the panel is opened.
-        mWebContents = WebContentsFactory.createWebContents(profile, true, false);
+        mWebContents = WebContentsFactory.createWebContents(mProfile, true, false);
 
         ContentView cv = ContentView.createContentView(
                 mActivity, null /* eventOffsetHandler */, mWebContents);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
index 4217f76..7906128 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
@@ -91,23 +91,12 @@
     /** Used for logging state changes. */
     private final ContextualSearchPanelMetrics mPanelMetrics;
 
-    /**
-     * The {@link CompositorViewHolder}, used as an anchor view. Also injected into other classes.
-     */
-    private final CompositorViewHolder mCompositorViewHolder;
-
-    /** The {@link WindowAndroid} for the current activity.  */
-    private final WindowAndroid mWindowAndroid;
-
     /** Used to query toolbar state. */
     private final ToolbarManager mToolbarManager;
 
     /** The {@link ActivityType} for the current activity. */
     private final @ActivityType int mActivityType;
 
-    /** Supplies the current {@link Tab} for the activity. */
-    private final Supplier<Tab> mCurrentTabSupplier;
-
     /** The distance of the divider from the end of the bar, in dp. */
     private final float mEndButtonWidthDp;
 
@@ -148,28 +137,39 @@
      * @param panelManager The object managing the how different panels are shown.
      * @param browserControlsStateProvider Used to measure the browser controls.
      * @param windowAndroid The {@link WindowAndroid} for the current activity.
+     * @param profile The Profile this ContextualSearchPanel is associated with.
      * @param compositorViewHolder The {@link CompositorViewHolder} for the current activity.
      * @param toolbarHeightDp The height of the toolbar in dp.
      * @param toolbarManager The {@link ToolbarManager}, used to query for colors.
      * @param activityType The {@link ActivityType} for the current activity.
      * @param currentTabSupplier Supplies the current activity tab.
      */
-    public ContextualSearchPanel(@NonNull Context context, @NonNull LayoutManagerImpl layoutManager,
+    public ContextualSearchPanel(
+            @NonNull Context context,
+            @NonNull LayoutManagerImpl layoutManager,
             @NonNull OverlayPanelManager panelManager,
             @NonNull BrowserControlsStateProvider browserControlsStateProvider,
             @NonNull WindowAndroid windowAndroid,
-            @NonNull CompositorViewHolder compositorViewHolder, float toolbarHeightDp,
-            @NonNull ToolbarManager toolbarManager, @ActivityType int activityType,
+            @NonNull Profile profile,
+            @NonNull CompositorViewHolder compositorViewHolder,
+            float toolbarHeightDp,
+            @NonNull ToolbarManager toolbarManager,
+            @ActivityType int activityType,
             @NonNull Supplier<Tab> currentTabSupplier) {
-        super(context, layoutManager, panelManager, browserControlsStateProvider, windowAndroid,
-                compositorViewHolder, toolbarHeightDp, currentTabSupplier);
+        super(
+                context,
+                layoutManager,
+                panelManager,
+                browserControlsStateProvider,
+                windowAndroid,
+                profile,
+                compositorViewHolder,
+                toolbarHeightDp,
+                currentTabSupplier);
         mSceneLayer = createNewContextualSearchSceneLayer();
         mPanelMetrics = new ContextualSearchPanelMetrics();
-        mCompositorViewHolder = compositorViewHolder;
-        mWindowAndroid = windowAndroid;
         mToolbarManager = toolbarManager;
         mActivityType = activityType;
-        mCurrentTabSupplier = currentTabSupplier;
 
         mEndButtonWidthDp = mContext.getResources().getDimensionPixelSize(
                                     R.dimen.contextual_search_padded_button_width)
@@ -178,9 +178,15 @@
 
     @Override
     public OverlayPanelContent createNewOverlayPanelContent() {
-        return new OverlayPanelContent(mManagementDelegate.getOverlayContentDelegate(),
-                new PanelProgressObserver(), mActivity, /* isIncognito= */ false, getBarHeight(),
-                mCompositorViewHolder, mWindowAndroid, mCurrentTabSupplier);
+        return new OverlayPanelContent(
+                mManagementDelegate.getOverlayContentDelegate(),
+                new PanelProgressObserver(),
+                mActivity,
+                getProfile(),
+                getBarHeight(),
+                getCompositorViewHolder(),
+                getWindowAndroid(),
+                getCurrentTabSupplier());
     }
 
     // ============================================================================================
@@ -326,7 +332,9 @@
         if (isPeeking()) {
             if (getSearchBarControl().getQuickActionControl().hasQuickAction()
                     && isCoordinateInsideActionTarget(x)) {
-                getSearchBarControl().getQuickActionControl().sendIntent(mCurrentTabSupplier.get());
+                getSearchBarControl()
+                        .getQuickActionControl()
+                        .sendIntent(getCurrentTabSupplier().get());
             } else {
                 // super takes care of expanding the Panel when peeking.
                 super.handleBarClick(x, y);
@@ -850,7 +858,7 @@
                         new PropertyModel.Builder(ScrimProperties.REQUIRED_KEYS)
                                 .with(ScrimProperties.TOP_MARGIN, 0)
                                 .with(ScrimProperties.AFFECTS_STATUS_BAR, true)
-                                .with(ScrimProperties.ANCHOR_VIEW, mCompositorViewHolder)
+                                .with(ScrimProperties.ANCHOR_VIEW, getCompositorViewHolder())
                                 .with(ScrimProperties.SHOW_IN_FRONT_OF_ANCHOR_VIEW, false)
                                 .with(ScrimProperties.VISIBILITY_CALLBACK, null)
                                 .with(ScrimProperties.CLICK_DELEGATE, null)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
index 1e44dd21..5483f8a6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
@@ -38,7 +38,6 @@
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerHost;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
@@ -290,8 +289,6 @@
     private View mToolbarContainerView;
     @Nullable private final TabDragSource mTabDragSource;
     private StripLayoutTab mActiveClickedTab;
-    private TabDropTarget mTabDropTarget;
-    private BrowserControlsStateProvider mBrowserControlStateProvider;
 
     // Tab Drag and Drop state to track if the dragged tab has been "torn" off of the tab strip.
     private boolean mDraggedTabOffStrip;
@@ -501,7 +498,6 @@
      */
     public void destroy() {
         mStripTabEventHandler.removeCallbacksAndMessages(null);
-        mTabDropTarget = null;
         if (mTabHoverCardView != null) {
             mTabHoverCardView.destroy();
             mTabHoverCardView = null;
@@ -1300,16 +1296,13 @@
 
     /**
      * Called on touch drag event.
-     * @param time   The current time of the app in ms.
-     * @param x      The y coordinate of the end of the drag event.
-     * @param y      The y coordinate of the end of the drag event.
+     *
+     * @param time The current time of the app in ms.
+     * @param x The y coordinate of the end of the drag event.
+     * @param y The y coordinate of the end of the drag event.
      * @param deltaX The number of pixels dragged in the x direction.
-     * @param deltaY The number of pixels dragged in the y direction.
-     * @param totalX The total delta x since the drag started.
-     * @param totalY The total delta y since the drag started.
      */
-    public void drag(
-            long time, float x, float y, float deltaX, float deltaY, float totalX, float totalY) {
+    public void drag(long time, float x, float y, float deltaX) {
         resetResizeTimeout(false);
 
         mLastUpdateTime = time;
@@ -3506,7 +3499,7 @@
                 // causes any conflict with images drop work.
                 boolean dragStarted =
                         mTabDragSource.startTabDragAction(
-                                mToolbarContainerView, this, tabBeingDragged, dragStartPointF);
+                                mToolbarContainerView, tabBeingDragged, dragStartPointF);
                 if (dragStarted) {
                     mActiveClickedTab = clickedTab;
                     mDraggedTabOffStrip = false;
@@ -3537,7 +3530,6 @@
         assert draggedTab != null;
 
         finishAnimationsAndPushTabUpdates();
-
         mDraggedTabOffStrip = false;
         draggedTab.setOffsetX(mLastOffsetX);
         draggedTab.setOffsetY(0);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
index 66677cc..8ac8147 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
@@ -176,7 +176,6 @@
     private final Supplier<LayerTitleCache> mLayerTitleCacheSupplier;
 
     // Drag-Drop
-    @Nullable private TabDropTarget mTabDropTarget;
     @Nullable private TabDragSource mTabDragSource;
 
     private class TabStripEventHandler implements MotionEventHandler {
@@ -202,7 +201,7 @@
         @Override
         public void drag(float x, float y, float dx, float dy, float tx, float ty) {
             mModelSelectorButton.drag(x, y);
-            getActiveStripLayoutHelper().drag(time(), x, y, dx, dy, tx, ty);
+            getActiveStripLayoutHelper().drag(time(), x, y, dx);
         }
 
         @Override
@@ -436,11 +435,11 @@
                 && TabUiFeatureUtilities.isTabDragEnabled()) {
             mTabDragSource =
                     new TabDragSource(
-                            toolbarContainerView,
+                            context,
+                            () -> getActiveStripLayoutHelper(),
                             multiInstanceManager,
                             dragDropDelegate,
                             browserControlsStateProvider);
-            mTabDropTarget = new TabDropTarget(this, multiInstanceManager, toolbarContainerView);
         }
 
         mNormalHelper =
@@ -521,7 +520,6 @@
             mTabModelSelectorTabModelObserver.destroy();
             mTabModelSelectorTabObserver.destroy();
         }
-        mTabDropTarget = null;
         mTabDragSource = null;
     }
 
@@ -717,8 +715,7 @@
      * Returns drag listener for tab strip.
      */
     public OnDragListener getDragListener() {
-        if (mTabDragSource == null) return null;
-        return mTabDragSource.getDragListener();
+        return mTabDragSource;
     }
 
     void setModelSelectorButtonVisibleForTesting(boolean isVisible) {
@@ -1039,8 +1036,4 @@
     public TabDragSource getTabDragSourceForTesting() {
         return mTabDragSource;
     }
-
-    public TabDropTarget getTabDropTargetForTesting() {
-        return mTabDropTarget;
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSource.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSource.java
index a7952793..aca7b575 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSource.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSource.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.compositor.overlays.strip;
 
 import android.app.Activity;
+import android.content.ClipData;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Color;
@@ -21,6 +22,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.Log;
+import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
@@ -31,6 +33,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabUtils;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
+import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.dragdrop.DragAndDropDelegate;
 import org.chromium.ui.dragdrop.DropDataAndroid;
 
@@ -39,26 +42,34 @@
  * drop process. The tab drag and drop is initiated from the active instance of {@link
  * StripLayoutHelper}.
  */
-public class TabDragSource {
+public class TabDragSource implements View.OnDragListener {
     private static final String TAG = "TabDragSource";
-
     private MultiInstanceManager mMultiInstanceManager;
-
     private DragAndDropDelegate mDragAndDropDelegate;
-    private StripLayoutHelper mSourceStripLayoutHelper;
-    private OnDragListenerImpl mOnDragListenerImpl;
-    private PointF mDragShadowOffset = new PointF(0, 0);
-    private float mTabStripHeightDp;
-    private float mPxToDp;
+    private Supplier<StripLayoutHelper> mStripLayoutHelperSupplier;
     private BrowserControlsStateProvider mBrowserControlStateProvider;
     private View mDragSourceView;
+    private PointF mDragShadowOffset = new PointF(0, 0);
+    private float mPxToDp;
+    private final float mTabStripHeightPx;
+
+    /** Drag Event Listener trackers * */
+    // Drag start screen position.
+    private PointF mStartScreenPos;
+
+    // Last drag positions relative to the source view. Set when drag starts or is moved within
+    // view.
+    private float mLastXDp;
+    private float mLastYDp;
+    private int mLastAction;
 
     /**
      * Prepares the toolbar view to listen to the drag events and data drop after the drag is
      * initiated.
      *
-     * @param toolbarContainerView @{link View} used to setup the drag and drop @{link
-     *     View.OnDragListener}.
+     * @param context @{@link Context} to get resources.
+     * @param stripLayoutHelperSupplier Supplier for @{@link StripLayoutHelper} to perform strip
+     *     actions.
      * @param multiInstanceManager @{link MultiInstanceManager} to perform move action when drop
      *     completes.
      * @param dragAndDropDelegate @{@link DragAndDropDelegate} to initiate tab drag and drop.
@@ -66,187 +77,196 @@
      *     drag-shadow dimens.
      */
     public TabDragSource(
-            @NonNull View toolbarContainerView,
+            @NonNull Context context,
+            @NonNull Supplier<StripLayoutHelper> stripLayoutHelperSupplier,
             @NonNull MultiInstanceManager multiInstanceManager,
             @NonNull DragAndDropDelegate dragAndDropDelegate,
             @NonNull BrowserControlsStateProvider browserControlStateProvider) {
-        mPxToDp =
-                1.f / toolbarContainerView.getContext().getResources().getDisplayMetrics().density;
+        mPxToDp = 1.f / context.getResources().getDisplayMetrics().density;
+        // TODO(crbug.com/1498252): Use Toolbar#getTabStripHeight() instead.
+        mTabStripHeightPx = context.getResources().getDimension(R.dimen.tab_strip_height);
+        mStripLayoutHelperSupplier = stripLayoutHelperSupplier;
         mMultiInstanceManager = multiInstanceManager;
         mDragAndDropDelegate = dragAndDropDelegate;
         mBrowserControlStateProvider = browserControlStateProvider;
-        // Save the tabs toolbar height as it occupies the top half of the toolbar container.
-        mTabStripHeightDp =
-                toolbarContainerView
-                        .getContext()
-                        .getResources()
-                        .getDimension(R.dimen.tab_strip_height);
-
-        mOnDragListenerImpl = new OnDragListenerImpl();
     }
 
     /**
-     * Returns Drag listener for tab strip.
+     * Starts the tab drag action by initiating the process by calling @{link
+     * View.startDragAndDrop}.
+     *
+     * @param toolbarContainerView @{link View} used to create the drag shadow.
+     * @param tabBeingDragged @{link Tab} is the selected tab being dragged.
+     * @param startPoint Position of the drag start point in view coordinates.
      */
-    public OnDragListenerImpl getDragListener() {
-        return mOnDragListenerImpl;
+    public boolean startTabDragAction(
+            @NonNull View toolbarContainerView,
+            @NonNull Tab tabBeingDragged,
+            @NonNull PointF startPoint) {
+        if (!TabUiFeatureUtilities.isTabDragEnabled()
+                || DragDropGlobalState.getInstance().dragSourceInstanceId
+                        != MultiWindowUtils.INVALID_INSTANCE_ID) {
+            return false;
+        }
+
+        setGlobalState(tabBeingDragged);
+
+        mDragSourceView = toolbarContainerView;
+        mDragShadowOffset =
+                TabUiFeatureUtilities.isTabDragAsWindowEnabled()
+                        ? getPositionOnScreen(toolbarContainerView, startPoint)
+                        : new PointF(0f, 0f);
+
+        DropDataAndroid dropData =
+                new ChromeDropDataAndroid.Builder().withTabId(tabBeingDragged.getId()).build();
+        DragShadowBuilder builder =
+                createTabDragShadowBuilder(toolbarContainerView.getContext(), false);
+        return mDragAndDropDelegate.startDragAndDrop(toolbarContainerView, builder, dropData);
     }
 
-    @VisibleForTesting
-    class OnDragListenerImpl implements View.OnDragListener {
-        private int mLastAction;
-        private float mStartXPosition;
-        private float mStartYPosition;
-        private float mLastXPosition;
-        private float mLastYPosition;
-        private boolean mPointerInView;
-        private boolean mDragShadowVisible;
-
-        @Override
-        public boolean onDrag(View view, DragEvent dragEvent) {
-            // Check if the events are being received in the possible drop targets.
-            if (canAcceptTabDrop(dragEvent)) return false;
-
-            // Since drag events are over Chrome window hence set the appropriate drag shadow, if
-            // required.
-            setDragShadow(dragEvent);
-
-            switch (dragEvent.getAction()) {
-                case DragEvent.ACTION_DRAG_STARTED:
-                    resetState();
-                    mStartXPosition = dragEvent.getX() * mPxToDp;
-                    mStartYPosition = dragEvent.getY() * mPxToDp;
-                    mLastXPosition = mStartXPosition;
-                    mLastYPosition = mStartYPosition;
-                    break;
-                case DragEvent.ACTION_DRAG_LOCATION:
-                    float curXPos = dragEvent.getX() * mPxToDp;
-                    float curYPos = dragEvent.getY() * mPxToDp;
-                    // TODO(b/285590087): Enter Android drag mode until tab is torn vertically to
-                    // prevent forwarding drag events back into SripLayoutHelper #drag,
-                    // #onUpOrCancel, #onDownInternal, etc.
-                    if (mPointerInView) {
-                        mSourceStripLayoutHelper.drag(LayoutManagerImpl.time(), curXPos, curYPos,
-                                curXPos - mLastXPosition, curYPos - mLastYPosition,
-                                curXPos - mStartXPosition, curYPos - mStartYPosition);
-                    }
-                    mLastXPosition = curXPos;
-                    mLastYPosition = curYPos;
-                    break;
-                case DragEvent.ACTION_DROP:
-                    if (mPointerInView) {
-                        mSourceStripLayoutHelper.onUpOrCancel(LayoutManagerImpl.time());
-                        mPointerInView = false;
-                    }
-                    break;
-                case DragEvent.ACTION_DRAG_ENDED:
-                    // Check if anyone handled the dropped ClipData meaning that drop was beyond
-                    // acceptable drop area.
-                    if (DragDropGlobalState.getInstance().tabBeingDragged != null
-                            && mLastAction == DragEvent.ACTION_DRAG_EXITED) {
-                        // Following call is device specific and is intended for specific platform
-                        // SysUI.
-                        sendPositionInfoToSysUI(view, mStartXPosition / mPxToDp,
-                                mStartYPosition / mPxToDp, dragEvent.getX(), dragEvent.getY());
-
-                        // Hence move the tab to a new Chrome window.
-                        openTabInNewWindow();
-                    }
-
-                    // Notify DragNDrop is completed.
-                    DragDropGlobalState.getInstance().reset();
-                    mSourceStripLayoutHelper.clearActiveClickedTab();
-                    break;
-                case DragEvent.ACTION_DRAG_ENTERED:
-                    mSourceStripLayoutHelper.dragActiveClickedTabOntoStrip(
-                            LayoutManagerImpl.time(), mLastXPosition);
-                    mPointerInView = true;
-                    break;
-                case DragEvent.ACTION_DRAG_EXITED:
-                    mSourceStripLayoutHelper.dragActiveClickedTabOutOfStrip(
-                            LayoutManagerImpl.time());
-                    mPointerInView = false;
-                    break;
-                default:
-                    break;
-            }
-
-            // Save the last drag situation to determine if the drop is outside toolbar view
-            mLastAction = dragEvent.getAction();
-            return false;
-        }
-
-        @VisibleForTesting
-        void resetState() {
-            // All the defined @{link DragEvent}.ACTION_* events are greater than zero hence the
-            // initial setting for last action can be zero.
-            mLastAction = 0;
-            mStartXPosition = 0.0f;
-            mStartYPosition = 0.0f;
-            mLastXPosition = 0.0f;
-            mLastYPosition = 0.0f;
-        }
-
-        @VisibleForTesting
-        boolean canAcceptTabDrop(DragEvent dragEvent) {
-            // If the event is received by a non source chrome window then mark to accept the drop
-            // in the destination chrome window only if drop is within the tabs strip area.
-            if (!isDragSource()) {
-                // Check if the drop is in the tabs strip area of the toolbar container.
-                // The container has two toolbars strips stacked on each other. The top one is the
-                // tabs strip layout and lower is for omnibox and navigation buttons. The tab drop
-                // is accepted only in the top tabs toolbar area only.
-                if (dragEvent.getAction() == DragEvent.ACTION_DROP) {
-                    DragDropGlobalState.getInstance().dropLocation =
-                            new PointF(dragEvent.getX() * mPxToDp, dragEvent.getY() * mPxToDp);
-                    if (inTabsToolbarArea(dragEvent)) {
-                        DragDropGlobalState.getInstance().acceptNextDrop = true;
-                    }
+    @Override
+    public boolean onDrag(View view, DragEvent dragEvent) {
+        // TODO(crbug.com/1497784): Check or supported mime in ClipData before proceeding.
+        boolean res = false;
+        switch (dragEvent.getAction()) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                res = onDragStart(dragEvent.getX(), dragEvent.getY());
+                break;
+            case DragEvent.ACTION_DRAG_ENDED:
+                res =
+                        onDragEnd(
+                                view,
+                                dragEvent.getX(),
+                                dragEvent.getY(),
+                                dragEvent.getResult(),
+                                mLastAction == DragEvent.ACTION_DRAG_EXITED);
+                break;
+            case DragEvent.ACTION_DRAG_ENTERED:
+                res = didOccurInTabStrip(dragEvent.getY()) ? onDragEnter() : false;
+                break;
+            case DragEvent.ACTION_DRAG_EXITED:
+                res = onDragExit();
+                break;
+            case DragEvent.ACTION_DRAG_LOCATION:
+                boolean isLastYInTabStrip = didOccurInTabStrip(mLastYDp / mPxToDp);
+                if (mLastAction == DragEvent.ACTION_DRAG_ENTERED
+                        || (isLastYInTabStrip && didOccurInTabStrip(dragEvent.getY()))) {
+                    // First move after drag enter OR drag moved within strip
+                    res = onDragLocation(dragEvent.getX(), dragEvent.getY());
+                } else if (isLastYInTabStrip) {
+                    // drag moved from within to outside strip.
+                    res = onDragExit();
+                } else if (didOccurInTabStrip(dragEvent.getY())) {
+                    // drag moved from outside to within strip.
+                    res = onDragEnter();
                 }
-                return true;
-            }
-            return false;
+                mLastXDp = dragEvent.getX() * mPxToDp;
+                mLastYDp = dragEvent.getY() * mPxToDp;
+                break;
+            case DragEvent.ACTION_DROP:
+                res =
+                        didOccurInTabStrip(dragEvent.getY())
+                                ? onDrop(view, dragEvent.getX(), dragEvent.getClipData())
+                                : false;
+                break;
+        }
+        mLastAction = dragEvent.getAction();
+        return res;
+    }
+
+    private boolean didOccurInTabStrip(float yPx) {
+        return yPx <= mTabStripHeightPx;
+    }
+
+    private boolean onDragStart(float xPx, float yPx) {
+        if (!isDragSource()) return true;
+        mStartScreenPos = new PointF(xPx, yPx);
+        mLastXDp = xPx * mPxToDp;
+        mLastYDp = yPx * mPxToDp;
+        return true;
+    }
+
+    private boolean onDragEnter() {
+        if (!isDragSource()) return false;
+        mStripLayoutHelperSupplier
+                .get()
+                .dragActiveClickedTabOntoStrip(LayoutManagerImpl.time(), mLastXDp);
+        return true;
+    }
+
+    private boolean onDragLocation(float xPx, float yPx) {
+        if (!isDragSource()) return false;
+        float xDp = xPx * mPxToDp;
+        float yDp = yPx * mPxToDp;
+        mStripLayoutHelperSupplier.get().drag(LayoutManagerImpl.time(), xDp, yDp, xDp - mLastXDp);
+        return true;
+    }
+
+    private boolean onDrop(View view, float xPx, ClipData clipData) {
+        if (isDragSource()) {
+            mStripLayoutHelperSupplier.get().onUpOrCancel(LayoutManagerImpl.time());
+            return true;
         }
 
-        private void showDragShadow(boolean show) {
-            assert mDragSourceView != null;
-            DragShadowBuilder builder =
-                    createTabDragShadowBuilder(mDragSourceView.getContext(), show);
-            mDragSourceView.updateDragShadow(builder);
-            mDragShadowVisible = show;
+        // If the event is received by a non source chrome window then accept the drop
+        // in the destination chrome window.
+        for (int i = 0; i < clipData.getItemCount(); i++) {
+            int sourceTabId = getTabIdFromClipData(clipData.getItemAt(i));
+            // Ignore the drop if the dropped tab id does not match the id of tab being
+            // dragged. Return the original payload drop for next in line to receive the
+            // drop to handle.
+            Tab tabBeingDragged = DragDropGlobalState.getInstance().tabBeingDragged;
+            if (tabBeingDragged == null || sourceTabId != tabBeingDragged.getId()) {
+                Log.w(TAG, "DnD: Received an invalid tab drop.");
+                return false;
+            }
+            int tabPositionIndex = getTabPositionIndex(xPx * mPxToDp);
+            // TODO(crbug.com/1497784): Pass the Activity explicitly in place of casting the
+            // context handle.
+            mMultiInstanceManager.moveTabToWindow(
+                    (Activity) view.getContext(), tabBeingDragged, tabPositionIndex);
+            mStripLayoutHelperSupplier.get().selectTabAtIndex(tabPositionIndex);
+        }
+        return true;
+    }
+
+    private boolean onDragEnd(
+            View view, float xPx, float yPx, boolean dropHandled, boolean didExitToolbar) {
+        if (!isDragSource()) return false;
+        // If tab was dragged and dropped out of source toolbar but the drop was not handled, move
+        // to a new window. TODO (crbug.com/1497784): Add isTabDragAsWindowEnabled() check below.
+        if (didExitToolbar
+                && !dropHandled
+                && DragDropGlobalState.getInstance().tabBeingDragged != null) {
+            // Following call is device specific and is intended for specific platform
+            // SysUI.
+            sendPositionInfoToSysUI(view, mStartScreenPos.x, mStartScreenPos.y, xPx, yPx);
+
+            // Hence move the tab to a new Chrome window.
+            mMultiInstanceManager.moveTabToNewWindow(
+                    DragDropGlobalState.getInstance().tabBeingDragged);
         }
 
-        private void setDragShadow(DragEvent dragEvent) {
-            // Only drag source can edit drag shadow.
-            assert isDragSource();
-            switch (dragEvent.getAction()) {
-                case DragEvent.ACTION_DRAG_LOCATION:
-                    boolean inTabsStripArea = inTabsToolbarArea(dragEvent);
-                    if (inTabsStripArea && mDragShadowVisible) {
-                        showDragShadow(false);
-                    }
-                    if (!inTabsStripArea && !mDragShadowVisible) {
-                        showDragShadow(true);
-                    }
-                    break;
-                case DragEvent.ACTION_DROP:
-                    showDragShadow(false);
-                    break;
-                case DragEvent.ACTION_DRAG_ENDED:
-                    showDragShadow(false);
-                    break;
-                case DragEvent.ACTION_DRAG_ENTERED:
-                    // No action here as the location is not specified. During the first
-                    // ACTION_DRAG_LOCATION event the correct drag shadow will be set.
-                    mDragShadowVisible = true;
-                    break;
-                case DragEvent.ACTION_DRAG_EXITED:
-                    showDragShadow(true);
-                    break;
-                default:
-                    break;
-            }
-        }
+        // Notify DragNDrop is completed.
+        DragDropGlobalState.getInstance().reset();
+        // TODO (crbug.com/1497784): Remove this method.
+        mStripLayoutHelperSupplier.get().clearActiveClickedTab();
+        return true;
+    }
+
+    private boolean onDragExit() {
+        if (!isDragSource()) return false;
+        // Show drag shadow when drag exits strip.
+        // TODO (crbug.com/1497784): Call this once on first drag exit. Reset on drag end.
+        showDragShadow(true);
+        mStripLayoutHelperSupplier.get().dragActiveClickedTabOutOfStrip(LayoutManagerImpl.time());
+        return true;
+    }
+
+    private void showDragShadow(boolean show) {
+        assert mDragSourceView != null;
+        DragShadowBuilder builder = createTabDragShadowBuilder(mDragSourceView.getContext(), show);
+        mDragSourceView.updateDragShadow(builder);
     }
 
     private boolean isDragSource() {
@@ -254,11 +274,45 @@
                 == mMultiInstanceManager.getCurrentInstanceId();
     }
 
+    private int getTabIdFromClipData(ClipData.Item item) {
+        // TODO(b/285585036): Expand the ClipData definition to support dropping of the Tab info to
+        // be used by SysUI that can parse this format.
+        String[] itemTexts = item.getText().toString().split(";");
+        String numberText = itemTexts[0].replaceAll("[^0-9]", "");
+        return numberText.isEmpty() ? Tab.INVALID_TAB_ID : Integer.parseInt(numberText);
+    }
+
+    private int getTabPositionIndex(float dropXDp) {
+        // Based on the location of the drop determine the position index where the tab will be
+        // placed.
+        StripLayoutHelper activeStripHelper = mStripLayoutHelperSupplier.get();
+        StripLayoutTab droppedOn = activeStripHelper.getTabAtPosition(dropXDp);
+        int tabPositionIndex = activeStripHelper.getTabCount();
+        // If not dropped on any existing tabs then simply add it at the end.
+        if (droppedOn != null) {
+            tabPositionIndex = activeStripHelper.findIndexForTab(droppedOn.getId());
+            // Check if the tab being moved needs to be added before or after the tab it was
+            // dropped on based on the layout direction of tabs.
+            float droppedTabCenterX = droppedOn.getDrawX() + droppedOn.getWidth() / 2.f;
+            if (LocalizationUtils.isLayoutRtl()) {
+                if (dropXDp <= droppedTabCenterX) {
+                    tabPositionIndex++;
+                }
+            } else {
+                if (dropXDp > droppedTabCenterX) {
+                    tabPositionIndex++;
+                }
+            }
+        }
+        return tabPositionIndex;
+    }
+
     @VisibleForTesting
-    void openTabInNewWindow() {
-        Tab tabBeingDragged = DragDropGlobalState.getInstance().tabBeingDragged;
-        assert tabBeingDragged != null;
-        mMultiInstanceManager.moveTabToNewWindow(tabBeingDragged);
+    void setGlobalState(Tab tabBeingDragged) {
+        // TODO (crbug.com/1497784): Move to startDragAndDrop call.
+        DragDropGlobalState.getInstance().tabBeingDragged = tabBeingDragged;
+        DragDropGlobalState.getInstance().dragSourceInstanceId =
+                mMultiInstanceManager.getCurrentInstanceId();
     }
 
     private static class TabDragShadowBuilder extends View.DragShadowBuilder {
@@ -293,48 +347,6 @@
         }
     }
 
-    /* Starts the tab drag action by initiating the process by calling @{link
-     * View.startDragAndDrop}.
-     *
-     * @param toolbarContainerView @{link View} used to create the drag shadow.
-     * @param sourceStripLayoutHelper @{link MultiInstanceManager} to forward drag message to @{link
-     *         StripLayoutHelper} for reodering tabs.
-     * @param tabBeingDragged @{link Tab} is the selected tab being dragged.
-     * @param startPoint Position of the drag start point in view coordinates.
-     */
-    public boolean startTabDragAction(
-            View toolbarContainerView,
-            StripLayoutHelper sourceStripLayoutHelper,
-            Tab tabBeingDragged,
-            PointF startPoint) {
-        if (!TabUiFeatureUtilities.isTabDragEnabled()) return false;
-        if (DragDropGlobalState.getInstance().dragSourceInstanceId
-                        != MultiWindowUtils.INVALID_INSTANCE_ID
-                || tabBeingDragged == null) return false;
-        assert (toolbarContainerView != null);
-        assert (sourceStripLayoutHelper != null);
-
-        setGlobalState(tabBeingDragged);
-
-        mSourceStripLayoutHelper = sourceStripLayoutHelper;
-        mDragSourceView = toolbarContainerView;
-        if (TabUiFeatureUtilities.isTabDragAsWindowEnabled()) {
-            mDragShadowOffset = getPositionOnScreen(toolbarContainerView, startPoint);
-        }
-
-        DropDataAndroid dropData =
-                new ChromeDropDataAndroid.Builder().withTabId(tabBeingDragged.getId()).build();
-        DragShadowBuilder builder =
-                createTabDragShadowBuilder(toolbarContainerView.getContext(), false);
-        return mDragAndDropDelegate.startDragAndDrop(toolbarContainerView, builder, dropData);
-    }
-
-    private void setGlobalState(Tab tabBeingDragged) {
-        DragDropGlobalState.getInstance().tabBeingDragged = tabBeingDragged;
-        DragDropGlobalState.getInstance().dragSourceInstanceId =
-                mMultiInstanceManager.getCurrentInstanceId();
-    }
-
     @NonNull
     @VisibleForTesting
     DragShadowBuilder createTabDragShadowBuilder(Context context, boolean show) {
@@ -383,18 +395,23 @@
         }
     }
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    void sendPositionInfoToSysUI(View view, float startXInView, float startYInView,
-            float endXInScreen, float endYInScreen) {
+    private void sendPositionInfoToSysUI(
+            View view,
+            float startXInView,
+            float startYInView,
+            float endXInScreen,
+            float endYInScreen) {
         // The start position is in the view coordinate system and related to the top left position
         // of the toolbar container view. Convert it to the screen coordinate system for comparison
         // with the drop position which is in screen coordinates.
         int[] topLeftLocation = new int[2];
+        // TODO (crbug.com/1497784): Use mDragSourceView instead.
         view.getLocationOnScreen(topLeftLocation);
         float startXInScreen = topLeftLocation[0] + startXInView;
         float startYInScreen = topLeftLocation[1] + startYInView;
 
         Activity activity = (Activity) view.getContext();
+        // TODO (crbug.com/1497784): Use WindowDelegate# getWindowVisibleDisplayFrame() instead.
         View decorView = activity.getWindow().getDecorView();
 
         // Compute relative offsets based on screen coords of the source window dimensions.
@@ -429,10 +446,16 @@
         intent.putExtra("CHROME_TAB_DRAG_DROP_ANCHOR_TASK_ID", activity.getTaskId());
         intent.putExtra("CHROME_TAB_DRAG_DROP_ANCHOR_OFFSET_X", xOffsetRelative2WindowWidth);
         intent.putExtra("CHROME_TAB_DRAG_DROP_ANCHOR_OFFSET_Y", yOffsetRelative2WindowHeight);
+        // TODO (crbug.com/1497784): Use WindowAndroid#sendBroadcast() instead.
         activity.sendBroadcast(intent);
-        Log.d(TAG,
-                "DnD Position info for SysUI: tId=" + activity.getTaskId() + ", xOff="
-                        + xOffsetRelative2WindowWidth + ", yOff=" + yOffsetRelative2WindowHeight);
+        Log.d(
+                TAG,
+                "DnD Position info for SysUI: tId="
+                        + activity.getTaskId()
+                        + ", xOff="
+                        + xOffsetRelative2WindowWidth
+                        + ", yOff="
+                        + yOffsetRelative2WindowHeight);
     }
 
     private PointF getPositionOnScreen(View view, PointF positionInView) {
@@ -443,14 +466,12 @@
         View decorView = ((Activity) view.getContext()).getWindow().getDecorView();
         decorView.getLocationOnScreen(topLeftLocationOfDecorView);
 
-        float positionXOnScreen = (topLeftLocationOfToolbarView[0] - topLeftLocationOfDecorView[0])
-                + positionInView.x / mPxToDp;
-        float positionYOnScreen = (topLeftLocationOfToolbarView[1] - topLeftLocationOfDecorView[1])
-                + positionInView.y / mPxToDp;
+        float positionXOnScreen =
+                (topLeftLocationOfToolbarView[0] - topLeftLocationOfDecorView[0])
+                        + positionInView.x / mPxToDp;
+        float positionYOnScreen =
+                (topLeftLocationOfToolbarView[1] - topLeftLocationOfDecorView[1])
+                        + positionInView.y / mPxToDp;
         return new PointF(positionXOnScreen, positionYOnScreen);
     }
-
-    private boolean inTabsToolbarArea(DragEvent dragEvent) {
-        return (dragEvent.getY() <= mTabStripHeightDp);
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDropTarget.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDropTarget.java
deleted file mode 100644
index 0e1645c..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDropTarget.java
+++ /dev/null
@@ -1,139 +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.
-
-package org.chromium.chrome.browser.compositor.overlays.strip;
-
-import android.app.Activity;
-import android.content.ClipData;
-import android.graphics.PointF;
-import android.util.Pair;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-import androidx.core.view.ContentInfoCompat;
-import androidx.core.view.OnReceiveContentListener;
-import androidx.core.view.ViewCompat;
-
-import org.chromium.base.Log;
-import org.chromium.chrome.browser.dragdrop.ChromeDragAndDropBrowserDelegate;
-import org.chromium.chrome.browser.dragdrop.DragDropGlobalState;
-import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
-import org.chromium.ui.base.LocalizationUtils;
-
-/**
- * The class manages receiving and handling the ClipData containing the Chrome Tab information
- * during the drag and drop process. It provides the callback for registration of the content
- * listener for the Toolbar container view.
- */
-class TabDropTarget implements OnReceiveContentListener {
-    private static final String TAG = "TabDropTarget";
-    private final MultiInstanceManager mMultiInstanceManager;
-    private final StripLayoutHelperManager mStripLayoutHelperManager;
-
-    TabDropTarget(
-            StripLayoutHelperManager stripLayoutHelperManager,
-            MultiInstanceManager multiInstanceManager,
-            View toolbarContainerView) {
-        mStripLayoutHelperManager = stripLayoutHelperManager;
-        mMultiInstanceManager = multiInstanceManager;
-        // Setup a drop target and register the callback where the drag events
-        // will be received.
-        ViewCompat.setOnReceiveContentListener(
-                toolbarContainerView,
-                new String[] {ChromeDragAndDropBrowserDelegate.CHROME_MIMETYPE_TAB},
-                this);
-    }
-
-    @Override
-    public @Nullable ContentInfoCompat onReceiveContent(View view, ContentInfoCompat payload) {
-        if (!TabUiFeatureUtilities.isTabDragEnabled()) return payload;
-        if (payload == null) return payload;
-
-        // Accept the drop to handle only if all the following conditions are met:
-        // 1. Tab Toolbar view is from a different Chrome window/instance
-        // 2. Tab being dragged is present
-        // 3. The item being dropped in the tabs strip area hence marked as accepted.
-        if (!isDragSource() && DragDropGlobalState.getInstance().acceptNextDrop) {
-            Pair<ContentInfoCompat, ContentInfoCompat> split =
-                    payload.partition(item -> item.getText() != null);
-            ContentInfoCompat uriContent = split.first;
-            ContentInfoCompat remainingContent = split.second;
-
-            if (uriContent != null) {
-                ClipData clip = uriContent.getClip();
-                for (int i = 0; i < clip.getItemCount(); i++) {
-                    int sourceTabId = getTabIdFromClipData(clip.getItemAt(i));
-                    // Ignore the drop if the dropped tab id does not match the id of tab being
-                    // dragged. Return the original payload drop for next in line to receive the
-                    // drop to handle.
-                    Tab tabBeingDragged = DragDropGlobalState.getInstance().tabBeingDragged;
-                    if (tabBeingDragged == null || sourceTabId != tabBeingDragged.getId()) {
-                        Log.w(TAG, "DnD: Received an invalid tab drop.");
-                        return payload;
-                    }
-                    int tabPositionIndex = getTabPositionIndex();
-                    // TODO(b/290648035): Pass the Activity explicitly in place of casting the
-                    // context handle.
-                    mMultiInstanceManager.moveTabToWindow(
-                            (Activity) view.getContext(), tabBeingDragged, tabPositionIndex);
-                    mStripLayoutHelperManager
-                            .getActiveStripLayoutHelper()
-                            .selectTabAtIndex(tabPositionIndex);
-                    DragDropGlobalState.getInstance().reset();
-                }
-            }
-
-            // Return anything that we didn't handle ourselves. This preserves the default
-            // platform behavior for text and anything else for which we are not implementing
-            // custom handling.
-            return remainingContent;
-        } else {
-            Log.w(TAG, "DnD: Received a drop but ignored the payload.");
-        }
-
-        return payload;
-    }
-
-    private boolean isDragSource() {
-        return DragDropGlobalState.getInstance().dragSourceInstanceId
-                == mMultiInstanceManager.getCurrentInstanceId();
-    }
-
-    int getTabIdFromClipData(ClipData.Item item) {
-        // TODO(b/285585036): Expand the ClipData definition to support dropping of the Tab info to
-        // be used by SysUI that can parse this format.
-        String[] itemTexts = item.getText().toString().split(";");
-        String numberText = itemTexts[0].replaceAll("[^0-9]", "");
-        return numberText.isEmpty() ? Tab.INVALID_TAB_ID : Integer.parseInt(numberText);
-    }
-
-    private int getTabPositionIndex() {
-        // Based on the location of the drop determine the position index where the tab will be
-        // placed.
-        PointF dropPosition = DragDropGlobalState.getInstance().dropLocation;
-        StripLayoutHelper activeStripHelper =
-                mStripLayoutHelperManager.getActiveStripLayoutHelper();
-        StripLayoutTab droppedOn = activeStripHelper.getTabAtPosition(dropPosition.x);
-        int tabPositionIndex = activeStripHelper.getTabCount();
-        // If not dropped on any existing tabs then simply add it at the end.
-        if (droppedOn != null) {
-            tabPositionIndex = activeStripHelper.findIndexForTab(droppedOn.getId());
-            // Check if the tab being moved needs to be added before or after the tab it was
-            // dropped on based on the layout direction of tabs.
-            float droppedTabCenterX = droppedOn.getDrawX() + droppedOn.getWidth() / 2.f;
-            if (LocalizationUtils.isLayoutRtl()) {
-                if (dropPosition.x <= droppedTabCenterX) {
-                    tabPositionIndex++;
-                }
-            } else {
-                if (dropPosition.x > droppedTabCenterX) {
-                    tabPositionIndex++;
-                }
-            }
-        }
-        return tabPositionIndex;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index 60ebae9..4fe672d1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -47,6 +47,7 @@
 import org.chromium.chrome.browser.gsa.GSAContextDisplaySelection;
 import org.chromium.chrome.browser.infobar.InfoBarContainer;
 import org.chromium.chrome.browser.layouts.SceneOverlay;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.SadTab;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabCreationState;
@@ -311,20 +312,27 @@
 
     /**
      * Initializes this manager.
+     *
      * @param parentView The parent view to attach Contextual Search UX to.
+     * @param profile The Profile associated with this ContextualSearchManager.
      * @param layoutManager A means of attaching the OverlayPanel to the scene.
-     * @param bottomSheetController The {@link BottomSheetController} that is used to show
-     *                              {@link BottomSheetContent}.
+     * @param bottomSheetController The {@link BottomSheetController} that is used to show {@link
+     *     BottomSheetContent}.
      * @param compositorViewHolder The {@link CompositorViewHolder} for the current activity.
      * @param toolbarHeightDp The height of the toolbar in dp.
      * @param toolbarManager The manager of the toolbar, used to query toolbar state.
      * @param activityType The type of the current activity.
      * @param intentRequestTracker The {@link IntentRequestTracker} of the current activity.
      */
-    public void initialize(@NonNull ViewGroup parentView, @NonNull LayoutManagerImpl layoutManager,
+    public void initialize(
+            @NonNull ViewGroup parentView,
+            @NonNull Profile profile,
+            @NonNull LayoutManagerImpl layoutManager,
             @NonNull BottomSheetController bottomSheetController,
-            @NonNull CompositorViewHolder compositorViewHolder, float toolbarHeightDp,
-            @NonNull ToolbarManager toolbarManager, @ActivityType int activityType,
+            @NonNull CompositorViewHolder compositorViewHolder,
+            float toolbarHeightDp,
+            @NonNull ToolbarManager toolbarManager,
+            @ActivityType int activityType,
             @NonNull IntentRequestTracker intentRequestTracker) {
         mNativeContextualSearchManagerPtr = ContextualSearchManagerJni.get().init(this);
 
@@ -339,10 +347,19 @@
             panel = new ContextualSearchPanelCoordinator(mActivity, mWindowAndroid,
                     bottomSheetController, this::getBasePageHeight, intentRequestTracker);
         } else {
-            panel = new ContextualSearchPanel(mActivity, mLayoutManager,
-                    mLayoutManager.getOverlayPanelManager(), mBrowserControlsStateProvider,
-                    mWindowAndroid, compositorViewHolder, toolbarHeightDp, toolbarManager,
-                    activityType, mTabSupplier);
+            panel =
+                    new ContextualSearchPanel(
+                            mActivity,
+                            mLayoutManager,
+                            mLayoutManager.getOverlayPanelManager(),
+                            mBrowserControlsStateProvider,
+                            mWindowAndroid,
+                            profile,
+                            compositorViewHolder,
+                            toolbarHeightDp,
+                            toolbarManager,
+                            activityType,
+                            mTabSupplier);
         }
 
         panel.setManagementDelegate(this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
index f4970c0..3cbcc941 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
@@ -367,7 +367,8 @@
      */
     static void tryUploadCrashDumpNow(File minidumpFile) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
-                && !ApplicationStatus.hasVisibleActivities()) {
+                && !(ApplicationStatus.isInitialized()
+                        && ApplicationStatus.hasVisibleActivities())) {
             // If we are on API 31+, Android does not allow us to start services from the
             // background. If we are in the background, then go through the JobScheduler path
             // instead. See crbug.com/1433529 for more details.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dragdrop/DragDropGlobalState.java b/chrome/android/java/src/org/chromium/chrome/browser/dragdrop/DragDropGlobalState.java
index 7b2ea49..913c6f7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/dragdrop/DragDropGlobalState.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/dragdrop/DragDropGlobalState.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.dragdrop;
 
-import android.graphics.PointF;
-
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.tab.Tab;
 
@@ -16,9 +14,6 @@
 
     public int dragSourceInstanceId = MultiWindowUtils.INVALID_INSTANCE_ID;
     public Tab tabBeingDragged;
-    public boolean acceptNextDrop;
-    public PointF dropLocation;
-
     public static DragDropGlobalState getInstance() {
         return sInstance;
     }
@@ -26,7 +21,5 @@
     public void reset() {
         dragSourceInstanceId = MultiWindowUtils.INVALID_INSTANCE_ID;
         tabBeingDragged = null;
-        acceptNextDrop = false;
-        dropLocation = null;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index 118f962..d48ccb7c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -543,15 +543,21 @@
         LayoutInflater inflater = LayoutInflater.from(activity);
         mNewTabPageLayout = (NewTabPageLayout) inflater.inflate(R.layout.new_tab_page_layout, null);
 
-        FeedActionDelegate actionDelegate = new FeedActionDelegateImpl(activity, snackbarManager,
-                mNewTabPageManager.getNavigationDelegate(), BookmarkModel.getForProfile(profile),
-                BrowserUiUtils.HostSurface.NEW_TAB_PAGE, mTabModelSelector) {
-            @Override
-            public void openHelpPage() {
-                NewTabPageUma.recordAction(NewTabPageUma.ACTION_CLICKED_LEARN_MORE);
-                super.openHelpPage();
-            }
-        };
+        FeedActionDelegate actionDelegate =
+                new FeedActionDelegateImpl(
+                        activity,
+                        snackbarManager,
+                        mNewTabPageManager.getNavigationDelegate(),
+                        BookmarkModel.getForProfile(profile),
+                        BrowserUiUtils.HostSurface.NEW_TAB_PAGE,
+                        mTabModelSelector,
+                        profile) {
+                    @Override
+                    public void openHelpPage() {
+                        NewTabPageUma.recordAction(NewTabPageUma.ACTION_CLICKED_LEARN_MORE);
+                        super.openHelpPage();
+                    }
+                };
 
         FeedSurfaceCoordinator feedSurfaceCoordinator = new FeedSurfaceCoordinator(activity,
                 snackbarManager, windowAndroid, mJankTracker,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/read_later/ReadingListUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/read_later/ReadingListUtils.java
index d07428b..35b4fe43b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/read_later/ReadingListUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/read_later/ReadingListUtils.java
@@ -14,6 +14,7 @@
 import org.chromium.chrome.browser.bookmarks.BookmarkModel;
 import org.chromium.chrome.browser.bookmarks.BookmarkUndoController;
 import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.components.bookmarks.BookmarkId;
@@ -66,8 +67,8 @@
     }
 
     /**
-     * Attempts to type swap and show the save flow when the "Add to reading list" menu item
-     * is selected but there's an existing bookmark.
+     * Attempts to type swap and show the save flow when the "Add to reading list" menu item is
+     * selected but there's an existing bookmark.
      *
      * @param activity The current Activity.
      * @param bottomsheetController The BottomsheetController, used to show the save flow.
@@ -76,10 +77,13 @@
      * @param bookmarkType The intended bookmark type.
      * @return Whether the given bookmark item has been type-swapped and the save flow shown.
      */
-    public static boolean maybeTypeSwapAndShowSaveFlow(@NonNull Activity activity,
+    public static boolean maybeTypeSwapAndShowSaveFlow(
+            @NonNull Activity activity,
             @NonNull BottomSheetController bottomsheetController,
-            @NonNull BookmarkModel bookmarkModel, @NonNull BookmarkId bookmarkId,
-            @BookmarkType int bookmarkType) {
+            @NonNull BookmarkModel bookmarkModel,
+            @NonNull BookmarkId bookmarkId,
+            @BookmarkType int bookmarkType,
+            @NonNull Profile profile) {
         if (bookmarkId == null || bookmarkId.getType() != BookmarkType.NORMAL
                 || bookmarkType != BookmarkType.READING_LIST) {
             return false;
@@ -98,9 +102,14 @@
 
         BookmarkId newBookmark = typeSwappedBookmarks.get(0);
         if (Boolean.TRUE.equals(sSkipShowSaveFlowForTesting)) return true;
-        BookmarkUtils.showSaveFlow(activity, bottomsheetController,
-                /*fromExplicitTrackUi=*/false, newBookmark,
-                /*wasBookmarkMoved=*/true, /*isNewBookmark=*/false);
+        BookmarkUtils.showSaveFlow(
+                activity,
+                bottomsheetController,
+                /* fromExplicitTrackUi= */ false,
+                newBookmark,
+                /* wasBookmarkMoved= */ true,
+                /* isNewBookmark= */ false,
+                profile);
         return true;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
index 3b5f4bd..199ee0d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
@@ -276,7 +276,12 @@
                 () -> {
                     // Add to reading list.
                     BookmarkUtils.addToReadingList(
-                            url, title, mSnackbarManager.get(), bookmarkModel, mTab.getContext());
+                            url,
+                            title,
+                            mSnackbarManager.get(),
+                            bookmarkModel,
+                            mTab.getContext(),
+                            profile);
                     TrackerFactory.getTrackerForProfile(profile)
                             .notifyEvent(EventConstants.READ_LATER_CONTEXT_MENU_TAPPED);
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java
index 5f5bccfd..667fb2bb 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java
@@ -31,6 +31,7 @@
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelState;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -61,6 +62,7 @@
     @Mock private LayoutManagerImpl mLayoutManager;
     @Mock private BrowserControlsStateProvider mBrowserControlsStateProvider;
     @Mock private ViewGroup mCompositorViewHolder;
+    @Mock private Profile mProfile;
     @Mock private Tab mTab;
 
     Activity mActivity;
@@ -76,6 +78,7 @@
                 OverlayPanelManager manager,
                 BrowserControlsStateProvider browserControlsStateProvider,
                 WindowAndroid windowAndroid,
+                Profile profile,
                 ViewGroup compositorViewHolder,
                 Tab tab) {
             super(
@@ -84,6 +87,7 @@
                     manager,
                     browserControlsStateProvider,
                     windowAndroid,
+                    profile,
                     compositorViewHolder,
                     MOCK_TOOLBAR_HEIGHT,
                     () -> tab);
@@ -120,6 +124,7 @@
                 OverlayPanelManager manager,
                 BrowserControlsStateProvider browserControlsStateProvider,
                 WindowAndroid windowAndroid,
+                Profile profile,
                 ViewGroup compositorViewHolder,
                 Tab tab) {
             super(
@@ -128,6 +133,7 @@
                     manager,
                     browserControlsStateProvider,
                     windowAndroid,
+                    profile,
                     compositorViewHolder,
                     tab);
         }
@@ -161,6 +167,7 @@
                                     panelManager,
                                     mBrowserControlsStateProvider,
                                     mWindowAndroid,
+                                    mProfile,
                                     mCompositorViewHolder,
                                     mTab);
                     mNoExpandPanel =
@@ -170,6 +177,7 @@
                                     panelManager,
                                     mBrowserControlsStateProvider,
                                     mWindowAndroid,
+                                    mProfile,
                                     mCompositorViewHolder,
                                     mTab);
                 });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java
index 7f2be6c..f3fb491 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java
@@ -32,6 +32,7 @@
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
 import org.chromium.chrome.browser.compositor.layouts.eventfilter.OverlayPanelEventFilter;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -64,6 +65,7 @@
     @Mock private LayoutManagerImpl mLayoutManager;
     @Mock private BrowserControlsStateProvider mBrowserControlsStateProvider;
     @Mock private ViewGroup mCompositorViewHolder;
+    @Mock private Profile mProfile;
     @Mock private Tab mTab;
     @Mock private OverlayContentDelegate mOverlayContentDelegate;
     @Mock private OverlayContentProgressObserver mOverlayContentProgressObserver;
@@ -142,6 +144,7 @@
                 OverlayPanelManager manager,
                 BrowserControlsStateProvider browserControlsStateProvider,
                 WindowAndroid windowAndroid,
+                Profile profile,
                 ViewGroup compositorViewHolder,
                 Tab tab) {
             super(
@@ -150,6 +153,7 @@
                     manager,
                     browserControlsStateProvider,
                     windowAndroid,
+                    profile,
                     compositorViewHolder,
                     MOCK_TOOLBAR_HEIGHT,
                     () -> tab);
@@ -167,7 +171,7 @@
                         mOverlayContentDelegate,
                         mOverlayContentProgressObserver,
                         mActivity,
-                        /* isIncognito= */ false,
+                        mProfile,
                         MOCK_TOOLBAR_HEIGHT,
                         mCompositorViewHolder,
                         mWindowAndroid,
@@ -281,6 +285,7 @@
                                     new OverlayPanelManager(),
                                     mBrowserControlsStateProvider,
                                     mWindowAndroid,
+                                    mProfile,
                                     mCompositorViewHolder,
                                     mTab);
                     mEventFilter = new OverlayPanelEventFilterWrapper(mActivity, mPanel);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java
index 7d43f83..828f04a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java
@@ -34,6 +34,7 @@
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.OverlayPanelManagerObserver;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.PanelPriority;
 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -60,6 +61,7 @@
     @Mock private LayoutManagerImpl mLayoutManager;
     @Mock private BrowserControlsStateProvider mBrowserControlsStateProvider;
     @Mock private ViewGroup mCompositorViewHolder;
+    @Mock private Profile mProfile;
     @Mock private Tab mTab;
 
     Activity mActivity;
@@ -82,6 +84,7 @@
                 OverlayPanelManager manager,
                 BrowserControlsStateProvider browserControlsStateProvider,
                 WindowAndroid windowAndroid,
+                Profile profile,
                 ViewGroup compositorViewHolder,
                 Tab tab,
                 @PanelPriority int priority,
@@ -92,6 +95,7 @@
                     manager,
                     browserControlsStateProvider,
                     windowAndroid,
+                    profile,
                     compositorViewHolder,
                     MOCK_TOOLBAR_HEIGHT,
                     () -> tab);
@@ -149,7 +153,7 @@
         /** Override creation and destruction of the WebContents as they rely on native methods. */
         private static class MockOverlayPanelContent extends OverlayPanelContent {
             public MockOverlayPanelContent() {
-                super(null, null, null, false, 0, null, null, null);
+                super(null, null, null, null, 0, null, null, null);
             }
 
             @Override
@@ -200,6 +204,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.MEDIUM,
@@ -223,6 +228,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.MEDIUM,
@@ -247,6 +253,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.LOW,
@@ -258,6 +265,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.HIGH,
@@ -282,6 +290,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.LOW,
@@ -293,6 +302,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.HIGH,
@@ -318,6 +328,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.LOW,
@@ -329,6 +340,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.HIGH,
@@ -354,6 +366,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.LOW,
@@ -365,6 +378,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.HIGH,
@@ -392,6 +406,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.LOW,
@@ -403,6 +418,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.MEDIUM,
@@ -414,6 +430,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.HIGH,
@@ -460,6 +477,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.LOW,
@@ -471,6 +489,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.MEDIUM,
@@ -482,6 +501,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.HIGH,
@@ -523,6 +543,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.MEDIUM,
@@ -539,6 +560,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.MEDIUM,
@@ -578,6 +600,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.LOW,
@@ -589,6 +612,7 @@
                         panelManager,
                         mBrowserControlsStateProvider,
                         mWindowAndroid,
+                        mProfile,
                         mCompositorViewHolder,
                         mTab,
                         PanelPriority.HIGH,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
index d5d84e50e..0a084f64 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java
@@ -19,6 +19,7 @@
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContent;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContentFactory;
 import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel;
+import org.chromium.chrome.browser.profiles.ProfileProvider;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.url.GURL;
@@ -424,7 +425,8 @@
                     contentDelegate,
                     progressObserver,
                     activity,
-                    false,
+                    ProfileProvider.getOrCreateProfile(
+                            activity.getProfileProviderSupplier().get(), false),
                     barHeight,
                     activity.getCompositorViewHolderForTesting(),
                     activity.getWindowAndroid(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java
index 9a98421..a6be1bb12 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java
@@ -103,8 +103,20 @@
         public ContextualSearchPanelWrapper(
                 Context context,
                 LayoutManagerImpl layoutManager,
-                OverlayPanelManager panelManager) {
-            super(context, layoutManager, panelManager, null, null, null, 0, null, 0, null);
+                OverlayPanelManager panelManager,
+                Profile profile) {
+            super(
+                    context,
+                    layoutManager,
+                    panelManager,
+                    null,
+                    null,
+                    profile,
+                    null,
+                    0,
+                    null,
+                    0,
+                    null);
         }
 
         @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTest.java
index a437fe2..92b391a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTest.java
@@ -18,6 +18,7 @@
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.profiles.ProfileProvider;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
@@ -44,7 +45,9 @@
                             new ContextualSearchPanelWrapper(
                                     activity,
                                     activity.getCompositorViewHolderForTesting().getLayoutManager(),
-                                    mPanelManager);
+                                    mPanelManager,
+                                    ProfileProvider.getOrCreateProfile(
+                                            activity.getProfileProviderSupplier().get(), false));
                     mPanel.setManagementDelegate(mContextualSearchManager);
                     mContextualSearchManager.setContextualSearchPanel(mPanel);
                     mPanelManager.setDynamicResourceLoader(new DynamicResourceLoader(0, null));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
index 496f6b7..36b26f6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
@@ -22,11 +22,6 @@
 import org.mockito.MockitoAnnotations;
 
 import org.chromium.base.Callback;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterProvider;
-import org.chromium.base.test.params.ParameterSet;
-import org.chromium.base.test.params.ParameterizedRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -37,7 +32,7 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.test.ChromeBrowserTestRule;
-import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.browser_ui.site_settings.ChosenObjectInfo;
 import org.chromium.components.browser_ui.site_settings.ContentSettingException;
@@ -69,8 +64,7 @@
 import java.util.concurrent.TimeoutException;
 
 /** Tests for WebsitePermissionsFetcher. */
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
+@RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({
     ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
     WebsitePermissionsFetcherTest.ENABLE_EXPERIMENTAL_WEB_PLATFORM_FEATURES,
@@ -301,29 +295,6 @@
                     entry("https://aol.com", "verizon.com"),
                     entry("https://vodafone.de", "vodafone.com"));
 
-    private static final List<Integer> EMBEDDED_CONTENT_SETTINGS =
-            Arrays.asList(ContentSettingsType.STORAGE_ACCESS);
-
-    private static final String ORIGIN = "https://google.com";
-    private static final String EMBEDDER = "https://embedder.com";
-    private static final String PREFERENCE_SOURCE = "preference";
-    private static final int EXPIRATION_IN_DAYS = 30;
-
-    /**
-     * Class to parameterize the params for {@link
-     * WebsitePermissionsFetcherTest.testFetchPreferencesForCategoryPermissionInfoTypes}, {@link
-     * WebsitePermissionsFetcherTest.testFetchPreferencesForCategoryContentSettingExceptionTypes},
-     * and {@link WebsitePermissionsFetcherTest.testFetchPreferencesForAdvancedCookieSettings}, .
-     */
-    public static class EmbargoedParams implements ParameterProvider {
-        @Override
-        public List<ParameterSet> getParameters() {
-            return Arrays.asList(
-                    new ParameterSet().value(true).name("Embargoed"),
-                    new ParameterSet().value(false).name("Normal"));
-        }
-    }
-
     private static class WebsitePermissionsWaiter extends CallbackHelper
             implements WebsitePermissionsFetcher.WebsitePermissionsCallback {
         @Override
@@ -567,43 +538,55 @@
         FakeWebsitePreferenceBridge websitePreferenceBridge = new FakeWebsitePreferenceBridge();
         fetcher.setWebsitePreferenceBridgeForTesting(websitePreferenceBridge);
 
+        String googleOrigin = "https://google.com";
+
         websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(ContentSettingsType.AR, ORIGIN, SITE_WILDCARD, false));
+                new PermissionInfo(ContentSettingsType.AR, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
                 new PermissionInfo(
-                        ContentSettingsType.IDLE_DETECTION, ORIGIN, SITE_WILDCARD, false));
+                        ContentSettingsType.IDLE_DETECTION, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(ContentSettingsType.GEOLOCATION, ORIGIN, SITE_WILDCARD, false));
+                new PermissionInfo(
+                        ContentSettingsType.GEOLOCATION, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(ContentSettingsType.MIDI, ORIGIN, SITE_WILDCARD, false));
+                new PermissionInfo(ContentSettingsType.MIDI, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(ContentSettingsType.MIDI_SYSEX, ORIGIN, SITE_WILDCARD, false));
+                new PermissionInfo(
+                        ContentSettingsType.MIDI_SYSEX, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
                 new PermissionInfo(
                         ContentSettingsType.PROTECTED_MEDIA_IDENTIFIER,
-                        ORIGIN,
+                        googleOrigin,
                         SITE_WILDCARD,
                         false));
         websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(ContentSettingsType.NFC, ORIGIN, SITE_WILDCARD, false));
+                new PermissionInfo(ContentSettingsType.NFC, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
                 new PermissionInfo(
-                        ContentSettingsType.NOTIFICATIONS, ORIGIN, SITE_WILDCARD, false));
+                        ContentSettingsType.NOTIFICATIONS, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
                 new PermissionInfo(
-                        ContentSettingsType.MEDIASTREAM_CAMERA, ORIGIN, SITE_WILDCARD, false));
+                        ContentSettingsType.MEDIASTREAM_CAMERA,
+                        googleOrigin,
+                        SITE_WILDCARD,
+                        false));
         websitePreferenceBridge.addPermissionInfo(
                 new PermissionInfo(
-                        ContentSettingsType.MEDIASTREAM_MIC, ORIGIN, SITE_WILDCARD, false));
+                        ContentSettingsType.MEDIASTREAM_MIC, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
                 new PermissionInfo(
-                        ContentSettingsType.CLIPBOARD_READ_WRITE, ORIGIN, SITE_WILDCARD, false));
+                        ContentSettingsType.CLIPBOARD_READ_WRITE,
+                        googleOrigin,
+                        SITE_WILDCARD,
+                        false));
         websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(ContentSettingsType.SENSORS, ORIGIN, SITE_WILDCARD, false));
+                new PermissionInfo(
+                        ContentSettingsType.SENSORS, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(ContentSettingsType.VR, ORIGIN, SITE_WILDCARD, false));
+                new PermissionInfo(ContentSettingsType.VR, googleOrigin, SITE_WILDCARD, false));
 
         // Add content setting exception types.
+        String preferenceSource = "preference";
         // If the ContentSettingsType.NUM_TYPES value changes *and* a new value has been exposed on
         // Android, then please update this code block to include a test for your new type.
         // Otherwise, just update count in the assert.
@@ -611,123 +594,127 @@
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.COOKIES,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.POPUPS,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.ADS,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.JAVASCRIPT,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.SOUND,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.BACKGROUND_SYNC,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.AUTOMATIC_DOWNLOADS,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.INSECURE_PRIVATE_NETWORK,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.JAVASCRIPT_JIT,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.AUTO_DARK_WEB_CONTENT,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.REQUEST_DESKTOP_SITE,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.FEDERATED_IDENTITY_API,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
         websitePreferenceBridge.addContentSettingException(
                 new ContentSettingException(
                         ContentSettingsType.ANTI_ABUSE,
-                        ORIGIN,
+                        googleOrigin,
                         ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
+                        preferenceSource,
                         /* isEmbargoed= */ false));
 
         // Add storage info.
         int storageSize = 256;
-        websitePreferenceBridge.addStorageInfo(new StorageInfo(ORIGIN, 0, storageSize));
+        websitePreferenceBridge.addStorageInfo(new StorageInfo(googleOrigin, 0, storageSize));
 
         // Add local storage info.
         websitePreferenceBridge.addLocalStorageInfoMapEntry(
-                new LocalStorageInfo(ORIGIN, storageSize, false));
+                new LocalStorageInfo(googleOrigin, storageSize, false));
 
         // Add shared dictionary info.
         int sharedDictionarySize = 12345;
         websitePreferenceBridge.addSharedDictionaryInfo(
-                new SharedDictionaryInfo(ORIGIN, ORIGIN, sharedDictionarySize));
+                new SharedDictionaryInfo(googleOrigin, googleOrigin, sharedDictionarySize));
 
         // Add chooser info types.
         websitePreferenceBridge.addChosenObjectInfo(
                 new ChosenObjectInfo(
-                        ContentSettingsType.USB_CHOOSER_DATA, ORIGIN, "Gadget", "Object", false));
+                        ContentSettingsType.USB_CHOOSER_DATA,
+                        googleOrigin,
+                        "Gadget",
+                        "Object",
+                        false));
         websitePreferenceBridge.addChosenObjectInfo(
                 new ChosenObjectInfo(
                         ContentSettingsType.BLUETOOTH_CHOOSER_DATA,
-                        ORIGIN,
+                        googleOrigin,
                         "Wireless",
                         "Object",
                         false));
@@ -737,7 +724,7 @@
                     Assert.assertEquals(1, sites.size());
                     Website site = sites.iterator().next();
 
-                    Assert.assertTrue(site.getAddress().matches(ORIGIN));
+                    Assert.assertTrue(site.getAddress().matches(googleOrigin));
 
                     // Check permission info types for |site|.
                     Assert.assertNotNull(site.getPermissionInfo(ContentSettingsType.GEOLOCATION));
@@ -819,12 +806,12 @@
                     Assert.assertEquals(1, storageInfos.size());
 
                     StorageInfo storageInfo = storageInfos.get(0);
-                    Assert.assertEquals(ORIGIN, storageInfo.getHost());
+                    Assert.assertEquals(googleOrigin, storageInfo.getHost());
                     Assert.assertEquals(storageSize, storageInfo.getSize());
 
                     // Check local storage info.
                     LocalStorageInfo localStorageInfo = site.getLocalStorageInfo();
-                    Assert.assertEquals(ORIGIN, localStorageInfo.getOrigin());
+                    Assert.assertEquals(googleOrigin, localStorageInfo.getOrigin());
                     Assert.assertEquals(storageSize, localStorageInfo.getSize());
                     Assert.assertFalse(localStorageInfo.isDomainImportant());
 
@@ -834,7 +821,7 @@
                     Assert.assertEquals(1, sharedDictionaryInfos.size());
 
                     SharedDictionaryInfo sharedDictionaryInfo = sharedDictionaryInfos.get(0);
-                    Assert.assertEquals(ORIGIN, sharedDictionaryInfo.getOrigin());
+                    Assert.assertEquals(googleOrigin, sharedDictionaryInfo.getOrigin());
                     Assert.assertEquals(sharedDictionarySize, sharedDictionaryInfo.getSize());
 
                     // Check chooser info types.
@@ -858,17 +845,19 @@
         FakeWebsitePreferenceBridge websitePreferenceBridge = new FakeWebsitePreferenceBridge();
         fetcher.setWebsitePreferenceBridgeForTesting(websitePreferenceBridge);
 
+        String googleOrigin = "https://google.com";
         String chromiumOrigin = "https://chromium.org";
         String exampleOrigin = "https://example.com";
 
         websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(ContentSettingsType.GEOLOCATION, ORIGIN, SITE_WILDCARD, false));
+                new PermissionInfo(
+                        ContentSettingsType.GEOLOCATION, googleOrigin, SITE_WILDCARD, false));
         websitePreferenceBridge.addPermissionInfo(
                 new PermissionInfo(
                         ContentSettingsType.GEOLOCATION, chromiumOrigin, SITE_WILDCARD, false));
 
         Website expectedGoogleWebsite =
-                new Website(WebsiteAddress.create(ORIGIN), WebsiteAddress.create(null));
+                new Website(WebsiteAddress.create(googleOrigin), WebsiteAddress.create(null));
         Website expectedChromiumWebsite =
                 new Website(WebsiteAddress.create(chromiumOrigin), WebsiteAddress.create(null));
 
@@ -879,11 +868,11 @@
                     // The order of |sites| is unknown, so check if the array contains a geolocation
                     // permission for each of the sites.
                     ArrayList<Website> siteArray = new ArrayList<>(sites);
-                    boolean containsOriginPermission = false;
+                    boolean containsGoogleOriginPermission = false;
                     boolean containsChromiumOriginPermission = false;
                     for (Website site : siteArray) {
                         if (site.compareByAddressTo(expectedGoogleWebsite) == 0) {
-                            containsOriginPermission = true;
+                            containsGoogleOriginPermission = true;
                         } else if (site.compareByAddressTo(expectedChromiumWebsite) == 0) {
                             containsChromiumOriginPermission = true;
                         }
@@ -892,7 +881,7 @@
                                 site.getPermissionInfo(ContentSettingsType.GEOLOCATION));
                     }
 
-                    Assert.assertTrue(containsOriginPermission);
+                    Assert.assertTrue(containsGoogleOriginPermission);
                     Assert.assertTrue(containsChromiumOriginPermission);
                 });
 
@@ -908,12 +897,12 @@
                     Assert.assertEquals(3, sites.size());
 
                     ArrayList<Website> siteArray = new ArrayList<>(sites);
-                    boolean containsOriginPermission = false;
+                    boolean containsGoogleOriginPermission = false;
                     boolean containsChromiumOriginPermission = false;
                     boolean containsExampleOriginPermission = false;
                     for (Website site : siteArray) {
                         if (site.compareByAddressTo(expectedGoogleWebsite) == 0) {
-                            containsOriginPermission = true;
+                            containsGoogleOriginPermission = true;
                         } else if (site.compareByAddressTo(expectedChromiumWebsite) == 0) {
                             containsChromiumOriginPermission = true;
                         } else if (site.compareByAddressTo(expectedExampleWebsite) == 0) {
@@ -924,7 +913,7 @@
                                 site.getPermissionInfo(ContentSettingsType.GEOLOCATION));
                     }
 
-                    Assert.assertTrue(containsOriginPermission);
+                    Assert.assertTrue(containsGoogleOriginPermission);
                     Assert.assertTrue(containsChromiumOriginPermission);
                     Assert.assertTrue(containsExampleOriginPermission);
                 });
@@ -943,13 +932,13 @@
 
     @Test
     @SmallTest
-    @UseMethodParameter(EmbargoedParams.class)
-    public void testFetchPreferencesForCategoryPermissionInfoTypes(boolean isEmbargoed) {
+    public void testFetchPreferencesForCategoryPermissionInfoTypes() {
         WebsitePermissionsFetcher fetcher =
                 new WebsitePermissionsFetcher(UNUSED_BROWSER_CONTEXT_HANDLE);
         FakeWebsitePreferenceBridge websitePreferenceBridge = new FakeWebsitePreferenceBridge();
         fetcher.setWebsitePreferenceBridgeForTesting(websitePreferenceBridge);
 
+        String googleOrigin = "https://google.com";
         // MIDI is excluded from this list because it does not have a top level category.
         ArrayList<Integer> permissionInfoTypes =
                 new ArrayList<>(
@@ -968,7 +957,7 @@
 
         for (@ContentSettingsType int type : permissionInfoTypes) {
             PermissionInfo fakePermissionInfo =
-                    new PermissionInfo(type, ORIGIN, SITE_WILDCARD, isEmbargoed);
+                    new PermissionInfo(type, googleOrigin, SITE_WILDCARD, false);
             websitePreferenceBridge.addPermissionInfo(fakePermissionInfo);
 
             fetcher.fetchPreferencesForCategory(
@@ -985,13 +974,15 @@
 
     @Test
     @SmallTest
-    @UseMethodParameter(EmbargoedParams.class)
-    public void testFetchPreferencesForCategoryContentSettingExceptionTypes(boolean isEmbargoed) {
+    public void testFetchPreferencesForCategoryContentSettingExceptionTypes() {
         WebsitePermissionsFetcher fetcher =
                 new WebsitePermissionsFetcher(UNUSED_BROWSER_CONTEXT_HANDLE);
         FakeWebsitePreferenceBridge websitePreferenceBridge = new FakeWebsitePreferenceBridge();
         fetcher.setWebsitePreferenceBridgeForTesting(websitePreferenceBridge);
 
+        String googleOrigin = "https://google.com";
+        String preferenceSource = "preference";
+        boolean isEmbargoed = false;
         ArrayList<Integer> contentSettingExceptionTypes =
                 new ArrayList<>(
                         Arrays.asList(
@@ -1010,9 +1001,9 @@
                 ContentSettingException fakeContentSettingException =
                         new ContentSettingException(
                                 type,
-                                ORIGIN,
+                                googleOrigin,
                                 ContentSettingValues.DEFAULT,
-                                PREFERENCE_SOURCE,
+                                preferenceSource,
                                 isEmbargoed);
                 websitePreferenceBridge.addContentSettingException(fakeContentSettingException);
 
@@ -1034,9 +1025,9 @@
                 ContentSettingException fakeContentSettingException =
                         new ContentSettingException(
                                 type,
-                                ORIGIN,
+                                googleOrigin,
                                 ContentSettingValues.BLOCK,
-                                PREFERENCE_SOURCE,
+                                preferenceSource,
                                 isEmbargoed);
                 websitePreferenceBridge.addContentSettingException(fakeContentSettingException);
 
@@ -1057,8 +1048,7 @@
 
     @Test
     @SmallTest
-    @UseMethodParameter(EmbargoedParams.class)
-    public void testFetchPreferencesForAdvancedCookieSettings(boolean isEmbargoed) {
+    public void testFetchPreferencesForAdvancedCookieSettings() {
         WebsitePermissionsFetcher fetcher =
                 new WebsitePermissionsFetcher(UNUSED_BROWSER_CONTEXT_HANDLE);
         FakeWebsitePreferenceBridge websitePreferenceBridge = new FakeWebsitePreferenceBridge();
@@ -1066,6 +1056,9 @@
 
         String mainSite = "https://a.com";
         String thirdPartySite = "https://b.com";
+        String preferenceSource = "preference";
+        Integer expirationInDays = 30;
+        boolean isEmbargoed = false;
         @ContentSettingsType int contentSettingsType = ContentSettingsType.COOKIES;
 
         // Test the advanced exception combinations of:
@@ -1088,8 +1081,8 @@
                                 pair.first,
                                 pair.second,
                                 ContentSettingValues.DEFAULT,
-                                PREFERENCE_SOURCE,
-                                EXPIRATION_IN_DAYS,
+                                preferenceSource,
+                                expirationInDays,
                                 isEmbargoed);
                 websitePreferenceBridge.addContentSettingException(fakeContentSettingException);
 
@@ -1114,8 +1107,8 @@
                                 pair.first,
                                 pair.second,
                                 ContentSettingValues.BLOCK,
-                                PREFERENCE_SOURCE,
-                                EXPIRATION_IN_DAYS,
+                                preferenceSource,
+                                expirationInDays,
                                 isEmbargoed);
                 websitePreferenceBridge.addContentSettingException(fakeContentSettingException);
 
@@ -1142,15 +1135,17 @@
         FakeWebsitePreferenceBridge websitePreferenceBridge = new FakeWebsitePreferenceBridge();
         fetcher.setWebsitePreferenceBridgeForTesting(websitePreferenceBridge);
 
+        String googleOrigin = "https://google.com";
         String chromiumOrigin = "https://chromium.org";
         int storageSize = 256;
         int sharedDictionarySize = 512;
-        StorageInfo fakeStorageInfo = new StorageInfo(ORIGIN, 0, storageSize);
-        LocalStorageInfo fakeLocalStorageInfo = new LocalStorageInfo(ORIGIN, storageSize, false);
+        StorageInfo fakeStorageInfo = new StorageInfo(googleOrigin, 0, storageSize);
+        LocalStorageInfo fakeLocalStorageInfo =
+                new LocalStorageInfo(googleOrigin, storageSize, false);
         LocalStorageInfo fakeImportantLocalStorageInfo =
                 new LocalStorageInfo(chromiumOrigin, storageSize, true);
         SharedDictionaryInfo fakeSharedDictionaryInfo =
-                new SharedDictionaryInfo(ORIGIN, ORIGIN, sharedDictionarySize);
+                new SharedDictionaryInfo(googleOrigin, googleOrigin, sharedDictionarySize);
 
         websitePreferenceBridge.addStorageInfo(fakeStorageInfo);
         websitePreferenceBridge.addLocalStorageInfoMapEntry(fakeLocalStorageInfo);
@@ -1198,7 +1193,7 @@
                     Assert.assertEquals(2, sites.size());
 
                     for (Website site : sites) {
-                        if (site.getAddress().matches(ORIGIN)) {
+                        if (site.getAddress().matches(googleOrigin)) {
                             List<StorageInfo> storageInfos = site.getStorageInfo();
                             Assert.assertEquals(1, storageInfos.size());
 
@@ -1248,6 +1243,7 @@
     @Test
     @SmallTest
     public void testFetchPreferencesForCategoryChooserDataTypes() {
+        String googleOrigin = "https://google.com";
         ArrayList<Integer> chooserDataTypes =
                 new ArrayList<>(
                         Arrays.asList(
@@ -1268,7 +1264,7 @@
             ChosenObjectInfo fakeObjectInfo =
                     new ChosenObjectInfo(
                             chooserDataType,
-                            ORIGIN,
+                            googleOrigin,
                             "Chosen Object",
                             "SerializedObjectData",
                             false);
@@ -1342,7 +1338,7 @@
                             ContentSettingsType.COOKIES,
                             origin,
                             ContentSettingValues.ALLOW,
-                            PREFERENCE_SOURCE,
+                            "preference",
                             /* isEmbargoed= */ false));
         }
 
@@ -1389,164 +1385,4 @@
                             });
                 });
     }
-
-    @Test
-    @SmallTest
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
-    public void testFetchAllSites() {
-        WebsitePermissionsFetcher fetcher =
-                new WebsitePermissionsFetcher(UNUSED_BROWSER_CONTEXT_HANDLE);
-        FakeWebsitePreferenceBridge websitePreferenceBridge = new FakeWebsitePreferenceBridge();
-        fetcher.setWebsitePreferenceBridgeForTesting(websitePreferenceBridge);
-
-        websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(ContentSettingsType.GEOLOCATION, ORIGIN, SITE_WILDCARD, false));
-        websitePreferenceBridge.addPermissionInfo(
-                new PermissionInfo(
-                        ContentSettingsType.GEOLOCATION, EMBEDDER, SITE_WILDCARD, false));
-        websitePreferenceBridge.addContentSettingException(
-                new ContentSettingException(
-                        ContentSettingsType.STORAGE_ACCESS,
-                        ORIGIN,
-                        EMBEDDER,
-                        ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
-                        EXPIRATION_IN_DAYS,
-                        /* isEmbargoed= */ false));
-        websitePreferenceBridge.addContentSettingException(
-                new ContentSettingException(
-                        ContentSettingsType.STORAGE_ACCESS,
-                        ORIGIN,
-                        null,
-                        ContentSettingValues.DEFAULT,
-                        PREFERENCE_SOURCE,
-                        EXPIRATION_IN_DAYS,
-                        /* isEmbargoed= */ true));
-
-        Website expectedWebsite =
-                new Website(WebsiteAddress.create(ORIGIN), WebsiteAddress.create(null));
-        Website expectedEmbedderWebsite =
-                new Website(WebsiteAddress.create(EMBEDDER), WebsiteAddress.create(null));
-
-        fetcher.fetchPreferencesForCategory(
-                SiteSettingsCategory.createFromType(
-                        UNUSED_BROWSER_CONTEXT_HANDLE, SiteSettingsCategory.Type.ALL_SITES),
-                (sites) -> {
-                    Assert.assertEquals(2, sites.size());
-
-                    // The order of |sites| is unknown, so check if the array contains a geolocation
-                    // permission for each of the sites.
-                    ArrayList<Website> siteArray = new ArrayList<>(sites);
-                    boolean containsOriginPermission = false;
-                    boolean containsEmbedderOriginPermission = false;
-                    for (Website site : siteArray) {
-                        if (site.compareByAddressTo(expectedWebsite) == 0) {
-                            containsOriginPermission = true;
-
-                            // Check that embargoed Storage Access is grouped by the origin.
-                            Assert.assertEquals(
-                                    Integer.valueOf(ContentSettingValues.DEFAULT),
-                                    site.getContentSetting(
-                                            UNUSED_BROWSER_CONTEXT_HANDLE,
-                                            ContentSettingsType.STORAGE_ACCESS));
-                            Assert.assertTrue(
-                                    site.getEmbeddedPermissions()
-                                            .get(ContentSettingsType.STORAGE_ACCESS)
-                                            .get(0)
-                                            .isEmbargoed());
-
-                        } else if (site.compareByAddressTo(expectedEmbedderWebsite) == 0) {
-                            containsEmbedderOriginPermission = true;
-
-                            // Check that a normal Storage Access is grouped by the embedder.
-                            Assert.assertEquals(
-                                    Integer.valueOf(ContentSettingValues.DEFAULT),
-                                    site.getContentSetting(
-                                            UNUSED_BROWSER_CONTEXT_HANDLE,
-                                            ContentSettingsType.STORAGE_ACCESS));
-                            Assert.assertFalse(
-                                    site.getEmbeddedPermissions()
-                                            .get(ContentSettingsType.STORAGE_ACCESS)
-                                            .get(0)
-                                            .isEmbargoed());
-                        }
-
-                        Assert.assertNotNull(
-                                site.getPermissionInfo(ContentSettingsType.GEOLOCATION));
-                    }
-
-                    Assert.assertTrue(containsOriginPermission);
-                    Assert.assertTrue(containsEmbedderOriginPermission);
-                });
-    }
-
-    @Test
-    @SmallTest
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
-    @UseMethodParameter(EmbargoedParams.class)
-    public void testFetchPreferencesForCategoryEmbeddedPermissionTypes(boolean isEmbargoed) {
-        WebsitePermissionsFetcher fetcher =
-                new WebsitePermissionsFetcher(UNUSED_BROWSER_CONTEXT_HANDLE);
-        FakeWebsitePreferenceBridge websitePreferenceBridge = new FakeWebsitePreferenceBridge();
-        fetcher.setWebsitePreferenceBridgeForTesting(websitePreferenceBridge);
-
-        String embedder = isEmbargoed ? null : EMBEDDER;
-
-        for (@ContentSettingsType int type : EMBEDDED_CONTENT_SETTINGS) {
-            ContentSettingException fakeContentSetting =
-                    new ContentSettingException(
-                            type,
-                            ORIGIN,
-                            embedder,
-                            ContentSettingValues.DEFAULT,
-                            PREFERENCE_SOURCE,
-                            EXPIRATION_IN_DAYS,
-                            isEmbargoed);
-            websitePreferenceBridge.addContentSettingException(fakeContentSetting);
-
-            fetcher.fetchPreferencesForCategory(
-                    SiteSettingsCategory.createFromContentSettingsType(
-                            UNUSED_BROWSER_CONTEXT_HANDLE, type),
-                    (sites) -> {
-                        Assert.assertEquals(1, sites.size());
-
-                        Website site = sites.iterator().next();
-                        List<ContentSettingException> exceptions =
-                                site.getEmbeddedPermissions().get(type);
-                        Assert.assertEquals(1, exceptions.size());
-                        assertContentSettingExceptionEquals(fakeContentSetting, exceptions.get(0));
-                    });
-        }
-    }
-
-    @SmallTest
-    @Test(expected = AssertionError.class)
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
-    @UseMethodParameter(EmbargoedParams.class)
-    public void testFailFetchPreferencesForCategoryEmbeddedPermissionTypes(boolean isEmbargoed) {
-        WebsitePermissionsFetcher fetcher =
-                new WebsitePermissionsFetcher(UNUSED_BROWSER_CONTEXT_HANDLE);
-        FakeWebsitePreferenceBridge websitePreferenceBridge = new FakeWebsitePreferenceBridge();
-        fetcher.setWebsitePreferenceBridgeForTesting(websitePreferenceBridge);
-
-        String embedder = isEmbargoed ? EMBEDDER : SITE_WILDCARD;
-
-        for (@ContentSettingsType int type : EMBEDDED_CONTENT_SETTINGS) {
-            ContentSettingException fakeContentSetting =
-                    new ContentSettingException(
-                            type,
-                            ORIGIN,
-                            embedder,
-                            ContentSettingValues.DEFAULT,
-                            PREFERENCE_SOURCE,
-                            EXPIRATION_IN_DAYS,
-                            isEmbargoed);
-            websitePreferenceBridge.addContentSettingException(fakeContentSetting);
-
-            fetcher.fetchPreferencesForCategory(
-                    SiteSettingsCategory.createFromContentSettingsType(
-                            UNUSED_BROWSER_CONTEXT_HANDLE, type),
-                    (sites) -> {});
-        }
-    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManagerTest.java
index 2048bd3..c182948 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManagerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManagerTest.java
@@ -637,9 +637,6 @@
         assertNotNull(
                 "Tab drag source should be set.",
                 mStripLayoutHelperManager.getTabDragSourceForTesting());
-        assertNotNull(
-                "Tab drop target should be set.",
-                mStripLayoutHelperManager.getTabDropTargetForTesting());
     }
 
     @Test
@@ -652,9 +649,6 @@
         assertNull(
                 "Tab drag source should not be set.",
                 mStripLayoutHelperManager.getTabDragSourceForTesting());
-        assertNull(
-                "Tab drop target should not be set.",
-                mStripLayoutHelperManager.getTabDropTargetForTesting());
     }
 
     @Test
@@ -668,9 +662,6 @@
         assertNull(
                 "Tab drag source should not be set.",
                 mStripLayoutHelperManager.getTabDragSourceForTesting());
-        assertNull(
-                "Tab drop target should not be set.",
-                mStripLayoutHelperManager.getTabDropTargetForTesting());
     }
 
     @Test
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
index 7c50759..fad835e 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
@@ -1492,7 +1492,7 @@
         setTabDragSourceMock();
         onLongPress_OnTab();
         // Verify drag invoked
-        verify(mTabDragSource).startTabDragAction(any(), any(), any(), any());
+        verify(mTabDragSource).startTabDragAction(any(), any(), any());
     }
 
     private void onLongPress_OnTab() {
@@ -1899,7 +1899,7 @@
         mStripLayoutHelper.startReorderModeAtIndexForTesting(2);
         float dragDistance = -100f;
         float startX = mStripLayoutHelper.getLastReorderXForTesting();
-        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance);
 
         // Verify background tabs are dimmed, while interacting tab and hovered group are not.
         StripLayoutTab[] tabs = mStripLayoutHelper.getStripLayoutTabsForTesting();
@@ -1939,7 +1939,7 @@
         mStripLayoutHelper.startReorderModeAtIndexForTesting(2);
         float dragDistance = -100f;
         float startX = mStripLayoutHelper.getLastReorderXForTesting();
-        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance);
 
         // Verify hovered group tab containers are visible.
         StripLayoutTab[] tabs = mStripLayoutHelper.getStripLayoutTabsForTesting();
@@ -2001,7 +2001,7 @@
         mStripLayoutHelper.startReorderModeAtIndexForTesting(2);
         float dragDistance = 100f;
         float startX = mStripLayoutHelper.getLastReorderXForTesting();
-        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance);
 
         // Assert the tabs swapped.
         assertEquals("Third and fourth tabs should have swapped.", thirdTab, tabs[3]);
@@ -2023,7 +2023,7 @@
         mStripLayoutHelper.startReorderModeAtIndexForTesting(3);
         float dragDistance = 60f;
         float startX = mStripLayoutHelper.getLastReorderXForTesting();
-        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance);
 
         // Verify fourth tab was dragged out of group, but not reordered.
         assertEquals("Fourth tab should not have moved.", fourthTab, tabs[3]);
@@ -2045,7 +2045,7 @@
         mStripLayoutHelper.startReorderModeAtIndexForTesting(0);
         float dragDistance = -60f;
         float startX = mStripLayoutHelper.getLastReorderXForTesting();
-        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance);
 
         // Verify first tab was dragged out of group, but not reordered.
         assertEquals("First tab should not have moved.", firstTab, tabs[0]);
@@ -2067,7 +2067,7 @@
         mStripLayoutHelper.startReorderModeAtIndexForTesting(4);
         float dragDistance = 60f;
         float startX = mStripLayoutHelper.getLastReorderXForTesting();
-        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance);
 
         // Verify fifth tab was dragged out of group, but not reordered.
         assertEquals("Fifth tab should not have moved.", fifthTab, tabs[4]);
@@ -2089,13 +2089,13 @@
         mStripLayoutHelper.startReorderModeAtIndexForTesting(0);
         float dragDistance = 300f;
         float startX = mStripLayoutHelper.getLastReorderXForTesting();
-        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance);
         // Verify no reordering, since we have not hovered over the tab group long enough.
         assertEquals("First tab should not have moved.", firstTab, tabs[0]);
 
         // Drag past the tab group.
         dragDistance = 650f;
-        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance);
         // Verify reordering, since we have dragged past the tab group.
         assertEquals("First tab should now be the fourth tab.", firstTab.getId(), tabs[3].getId());
     }
@@ -2116,7 +2116,7 @@
         mStripLayoutHelper.startReorderModeAtIndexForTesting(2);
         float dragDistance = -200f;
         float startX = mStripLayoutHelper.getLastReorderXForTesting();
-        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP, startX + dragDistance, 0f, dragDistance);
 
         // Verify state has not yet changed.
         tabs = mStripLayoutHelper.getStripLayoutTabsForTesting();
@@ -2129,8 +2129,7 @@
         dragDistance = -10;
         startX = mStripLayoutHelper.getLastReorderXForTesting();
         long timeDelta = StripLayoutHelper.DROP_INTO_GROUP_MS;
-        mStripLayoutHelper.drag(
-                TIMESTAMP + timeDelta, startX + dragDistance, 0f, dragDistance, 0f, 0f, 0f);
+        mStripLayoutHelper.drag(TIMESTAMP + timeDelta, startX + dragDistance, 0f, dragDistance);
 
         // Verify interacting tab was merged into group at the second index.
         tabs = mStripLayoutHelper.getStripLayoutTabsForTesting();
@@ -2396,8 +2395,7 @@
 
         // Act: Drag and update layout.
         float dragDeltaX = -200.f;
-        float totalY = 85.f; // totalY > 50.f to cross reorder threshold.
-        mStripLayoutHelper.drag(TIMESTAMP, 374.74f, 24.276f, dragDeltaX, -0.304f, -16.078f, totalY);
+        mStripLayoutHelper.drag(TIMESTAMP, 374.74f, 24.276f, dragDeltaX);
 
         float expectedOffset = -350; // mScrollOffset + dragDeltaX = -200 - 150 = -350
         // Assert scroll offset position.
@@ -2821,7 +2819,7 @@
     }
 
     private void setTabDragSourceMock() {
-        when(mTabDragSource.startTabDragAction(any(), any(), any(), any())).thenReturn(true);
+        when(mTabDragSource.startTabDragAction(any(), any(), any())).thenReturn(true);
 
         try {
             mContextForDragDrop = Mockito.spy(ContextUtils.getApplicationContext());
@@ -2857,7 +2855,7 @@
         // Act and verify.
         mStripLayoutHelper.allowMovingTabOutOfStripLayout(theClickedTab, DRAG_START_POINT);
 
-        verify(mTabDragSource, times(1)).startTabDragAction(any(), any(), any(), any());
+        verify(mTabDragSource, times(1)).startTabDragAction(any(), any(), any());
         assertTrue(
                 "Tab being dragged should exist during drag action.",
                 mStripLayoutHelper.getActiveClickedTabForTesting() != null);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSourceTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSourceTest.java
index 847c1b0..24839e1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSourceTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDragSourceTest.java
@@ -5,25 +5,27 @@
 package org.chromium.chrome.browser.compositor.overlays.strip;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipData.Item;
 import android.graphics.Point;
 import android.graphics.PointF;
-import android.os.Parcel;
 import android.view.DragEvent;
 import android.view.View;
 import android.view.View.DragShadowBuilder;
-import android.view.View.OnDragListener;
 import android.view.ViewGroup.MarginLayoutParams;
 
 import org.junit.After;
@@ -39,6 +41,7 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.dragdrop.DragDropGlobalState;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -56,11 +59,16 @@
 /** Tests for {@link TabDragSource}. */
 @EnableFeatures(ChromeFeatureList.TAB_LINK_DRAG_DROP_ANDROID)
 @RunWith(BaseRobolectricTestRunner.class)
+@Config(qualifiers = "sw600dp")
 public class TabDragSourceTest {
 
     public static final int CURR_INSTANCE_ID = 100;
     public static final int TAB_ID = 1;
-    private static final int ANOTHER_INSTANCE_ID = 101;
+    private static final float POS_X = 20f;
+    private static final float DRAG_MOVE_DISTANCE = 5f;
+    private static final String[] SUPPORTED_MIME_TYPES = {"chrome/tab"};
+    private static final int TAB_ID_NOT_DRAGGED = 2;
+    private float mPosY;
     @Rule public MockitoRule mMockitoProcessorRule = MockitoJUnit.rule();
     @Rule public TestRule mFeaturesProcessorRule = new Features.JUnitProcessor();
     @Mock private MultiInstanceManager mMultiInstanceManager;
@@ -73,22 +81,18 @@
     private TabDragSource mTabDragSource;
     private View mTabsToolbarView;
     private Tab mTabBeingDragged;
-    private static final float TAB_STRIP_HEIGHT = 22.f;
-    private static final float TAB_STRIP_X_START = 80.f;
-    private static final float TAB_STRIP_Y_START = 7.f;
-    private static final float TAB_STRIP_Y_STEP = 10.f;
-    private static final float TAB_X_OFFSET = 80.f;
-    private static final float TAB_Y_OFFSET_WITHIN = TAB_STRIP_HEIGHT - 1.f;
-    private static final float TAB_Y_OFFSET_OUTSIDE = TAB_STRIP_HEIGHT + 1.f;
     private static final float DROP_X_SCREEN_POS = 1000.f;
     private static final float DROP_Y_SCREEN_POS = 500.f;
-    private static final PointF DRAG_START_POINT = new PointF(TAB_X_OFFSET, TAB_Y_OFFSET_WITHIN);
+    private static final PointF DRAG_START_POINT = new PointF(0, 0);
+    private int mTabStripHeight;
 
     /** Resets the environment before each test. */
     @Before
     public void beforeTest() {
         mActivity = Robolectric.setupActivity(Activity.class);
         mActivity.setTheme(org.chromium.chrome.R.style.Theme_BrowserUI);
+        mTabStripHeight = mActivity.getResources().getDimensionPixelSize(R.dimen.tab_strip_height);
+        mPosY = mTabStripHeight - 2 * DRAG_MOVE_DISTANCE;
 
         // Create and spy on a simulated tab view.
         mTabsToolbarView = new View(mActivity);
@@ -100,7 +104,8 @@
 
         mTabDragSource =
                 new TabDragSource(
-                        mTabsToolbarView,
+                        mActivity,
+                        () -> mStripLayoutHelper,
                         mMultiInstanceManager,
                         mDragDropDelegate,
                         mBrowserControlsStateProvider);
@@ -111,18 +116,6 @@
         DragDropGlobalState.getInstance().reset();
     }
 
-    private DragEvent createDragEvent(int action, float x, float y, int result) {
-        Parcel parcel = Parcel.obtain();
-        parcel.writeInt(action);
-        parcel.writeFloat(x);
-        parcel.writeFloat(y);
-        parcel.writeInt(result); // Result
-        parcel.writeInt(0); // No Clipdata
-        parcel.writeInt(0); // No Clip Description
-        parcel.setDataPosition(0);
-        return DragEvent.CREATOR.createFromParcel(parcel);
-    }
-
     @EnableFeatures({ChromeFeatureList.TAB_DRAG_DROP_ANDROID})
     @DisableFeatures(ChromeFeatureList.TAB_LINK_DRAG_DROP_ANDROID)
     @Test
@@ -135,7 +128,7 @@
         // Act and verify.
         boolean res =
                 mTabDragSource.startTabDragAction(
-                        mTabsToolbarView, mStripLayoutHelper, mTabBeingDragged, DRAG_START_POINT);
+                        mTabsToolbarView, mTabBeingDragged, DRAG_START_POINT);
         assertTrue("startTabDragAction returned false.", res);
         verify(mDragDropDelegate)
                 .startDragAndDrop(
@@ -164,7 +157,7 @@
         // Act and verify.
         boolean res =
                 mTabDragSource.startTabDragAction(
-                        mTabsToolbarView, mStripLayoutHelper, mTabBeingDragged, DRAG_START_POINT);
+                        mTabsToolbarView, mTabBeingDragged, DRAG_START_POINT);
         assertTrue("startTabDragAction returned false.", res);
         verify(mDragDropDelegate)
                 .startDragAndDrop(
@@ -182,303 +175,190 @@
     }
 
     @Test
-    public void test_startTabDragAction_ReturnsFalseForInvalidTab() {
-        // Act and verify.
-        boolean res =
-                mTabDragSource.startTabDragAction(
-                        mTabsToolbarView, mStripLayoutHelper, null, DRAG_START_POINT);
-        assertFalse("startTabDragAction returned true.", res);
-        verify(mDragDropDelegate, never())
-                .startDragAndDrop(
-                        eq(mTabsToolbarView),
-                        any(DragShadowBuilder.class),
-                        any(DropDataAndroid.class));
+    public void test_startTabDragAction_ExceptionForInvalidTab() {
+        assertThrows(
+                NullPointerException.class,
+                () -> mTabDragSource.startTabDragAction(mTabsToolbarView, null, DRAG_START_POINT));
     }
 
     @Test
-    public void test_OnDragListenerImpl_SimulateDragDropWithinStripLayout_ReturnsSuccess() {
+    public void test_DragDropWithinStrip_ReturnsSuccess() {
         // Call startDrag to set class variables.
-        mTabDragSource.startTabDragAction(
-                mTabsToolbarView, mStripLayoutHelper, mTabBeingDragged, DRAG_START_POINT);
+        mTabDragSource.startTabDragAction(mTabsToolbarView, mTabBeingDragged, DRAG_START_POINT);
 
-        // Perform drag n drop simulation actions for movement within the strip layout.
-        simulateDragDropEvents(/* withinStripLayout= */ true);
+        /**
+         * Perform drag n drop simulation actions for movement within the strip layout. Drag start
+         * -> enter -> 2 moves within strip -> drop -> end.
+         */
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_LOCATION, POS_X + DRAG_MOVE_DISTANCE, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_LOCATION, POS_X + 2 * DRAG_MOVE_DISTANCE, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DROP, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED, 0f, 0f);
 
         // Verify appropriate events are generated to simulate movement within the strip layout.
+        // Drag tab onto strip on drag enter.
         verify(mStripLayoutHelper, times(1)).dragActiveClickedTabOntoStrip(anyLong(), anyFloat());
-        verify(mStripLayoutHelper, times(4))
-                .drag(
-                        anyLong(),
-                        anyFloat(),
-                        anyFloat(),
-                        anyFloat(),
-                        anyFloat(),
-                        anyFloat(),
-                        anyFloat());
-        verify(mStripLayoutHelper, times(TAB_ID)).onUpOrCancel(anyLong());
-    }
-
-    /**
-     * Tests the instance of the local class {@link TabDragSource#OnDragListenerImpl}.
-     *
-     * <p>Checks that it successfully sends the drag events to the {@link StripLayoutHelper}.
-     */
-    @Test
-    public void test_OnDragListenerImpl_SimulateDragDropOutsideStripLayout_ReturnsSuccess() {
-        // Call startDrag to set class variables.
-        mTabDragSource.startTabDragAction(
-                mTabsToolbarView, mStripLayoutHelper, mTabBeingDragged, DRAG_START_POINT);
-
-        // Perform drag n drop simulation actions for movement outside the strip layout.
-        simulateDragDropEvents(/* withinStripLayout= */ false);
-
-        // Verify appropriate events are generated to simulate movement outside the strip layout.
-        verify(mStripLayoutHelper, times(1)).dragActiveClickedTabOntoStrip(anyLong(), anyFloat());
-        verify(mStripLayoutHelper, times(1)).dragActiveClickedTabOutOfStrip(anyLong());
-        verify(mStripLayoutHelper, times(5))
-                .drag(
-                        anyLong(),
-                        anyFloat(),
-                        anyFloat(),
-                        anyFloat(),
-                        anyFloat(),
-                        anyFloat(),
-                        anyFloat());
+        // Invoke drag on drag moves.
+        verify(mStripLayoutHelper, times(2)).drag(anyLong(), anyFloat(), anyFloat(), anyFloat());
+        // Stop reorder on drop.
+        verify(mStripLayoutHelper, times(1)).onUpOrCancel(anyLong());
+        // Verify tab is not moved.
+        verify(mMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
+        verify(mMultiInstanceManager, times(0)).moveTabToWindow(any(), any(), anyInt());
+        // Verify clear.
+        verify(mStripLayoutHelper, times(1)).clearActiveClickedTab();
     }
 
     @Test
-    public void
-            test_OnDragListenerImpl_ForWithinStripMovement_NoNewWindowIsOpened_ReturnsSuccess() {
+    public void test_DragOutsideStrip_ReturnsSuccess() {
         // Call startDrag to set class variables.
-        mTabDragSource.startTabDragAction(
-                mTabsToolbarView, mStripLayoutHelper, mTabBeingDragged, DRAG_START_POINT);
+        mTabDragSource.startTabDragAction(mTabsToolbarView, mTabBeingDragged, DRAG_START_POINT);
 
-        // Perform drag n drop simulation actions for movement within the strip layout.
-        simulateDragDropEvents(/* withinStripLayout= */ true);
+        /**
+         * Perform drag n drop simulation actions for movement outside the strip area. Drag start ->
+         * enter -> move within strip -> move out of strip but within toolbar -> move back to strip
+         * -> exit.
+         */
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED, POS_X, mPosY);
+        // Move within the tab strip area.
+        triggerDragEvent(DragEvent.ACTION_DRAG_LOCATION, POS_X, mPosY + DRAG_MOVE_DISTANCE);
+        // Move outside the tab strip area but inside the toolbar.
+        triggerDragEvent(DragEvent.ACTION_DRAG_LOCATION, POS_X, mPosY + 3 * DRAG_MOVE_DISTANCE);
+        triggerDragEvent(
+                DragEvent.ACTION_DRAG_LOCATION,
+                POS_X,
+                mPosY + 2 * DRAG_MOVE_DISTANCE); // Back to within the tabs area.
+        triggerDragEvent(
+                DragEvent.ACTION_DRAG_EXITED, POS_X, mTabStripHeight + 10 * DRAG_MOVE_DISTANCE);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED, DROP_X_SCREEN_POS, DROP_Y_SCREEN_POS);
 
-        // Verify Since the drop is within the TabToolbar then no tab is move out to a
-        // new/existing Chrome Window.
+        // Verify appropriate events are generated to simulate movement outside the strip area.
+        // Drag tab onto strip on drag enter. Enter occurs twice.
+        verify(mStripLayoutHelper, times(2)).dragActiveClickedTabOntoStrip(anyLong(), anyFloat());
+        // Move within strip.
+        verify(mStripLayoutHelper, times(1)).drag(anyLong(), anyFloat(), anyFloat(), anyFloat());
+        // Drag tab out of strip on drag exit. Exit occurs twice.
+        verify(mStripLayoutHelper, times(2)).dragActiveClickedTabOutOfStrip(anyLong());
+        // Verify Since the drop is outside the TabToolbar area the tab will be move to a new
+        // Chrome Window.
+        verify(mMultiInstanceManager, times(1)).moveTabToNewWindow(mTabBeingDragged);
+        // Verify tab cleared.
+        verify(mStripLayoutHelper, times(1)).clearActiveClickedTab();
+    }
+
+    @Test
+    public void test_DropInSourceTabStrip_DontMoveTabToOtherWindow() {
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DROP, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED, POS_X, mPosY);
+
+        // Verify - Move tab is not invoked.
+        verify(mMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
+        verify(mMultiInstanceManager, times(0)).moveTabToWindow(any(), any(), anyInt());
+    }
+
+    @Test
+    public void test_DropInSourceToolbar_NoOp() {
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DROP, POS_X, mTabStripHeight + 5);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED, POS_X, mTabStripHeight + 5);
+
+        verifyNoInteractions(mStripLayoutHelper);
+        verify(mMultiInstanceManager, times(0)).moveTabToWindow(any(), any(), anyInt());
+        verify(mMultiInstanceManager, times(0)).moveTabToNewWindow(any());
+    }
+
+    @Test
+    public void test_DropInDestinationStripOnFirstHalfOfTab_MoveTabToDestinationAtIndex() {
+        // Set state.
+        mTabDragSource.setGlobalState(mTabBeingDragged);
+        // Simulate destination instance.
+        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(CURR_INSTANCE_ID + 1);
+        // Mock strip actions.
+        StripLayoutTab stripTab = mock(StripLayoutTab.class);
+        float tabWidth = 5f;
+        when(stripTab.getWidth()).thenReturn(tabWidth);
+        // Tab drop POS_X is at left half of tab.
+        when(stripTab.getDrawX()).thenReturn(POS_X - (tabWidth / 2));
+        when(stripTab.getId()).thenReturn(10);
+        when(mStripLayoutHelper.findIndexForTab(10)).thenReturn(2);
+        when(mStripLayoutHelper.getTabAtPosition(POS_X)).thenReturn(stripTab);
+
+        // Trigger drop in tab strip.
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DROP, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED, POS_X, mPosY);
+
+        // Verify - Tab moved to destination window at index.
+        verify(mMultiInstanceManager, times(1)).moveTabToWindow(any(), eq(mTabBeingDragged), eq(2));
         verify(mMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
     }
 
     @Test
-    public void test_OnDragListenerImpl_ForOutsideStripMovement_NewWindowIsOpened_ReturnsSuccess() {
-        // Call startDrag to set class variables.
-        mTabDragSource.startTabDragAction(
-                mTabsToolbarView, mStripLayoutHelper, mTabBeingDragged, DRAG_START_POINT);
+    public void test_DropInDestinationStripOnLaterHalfOfTab_MoveTabToDestinationAtIndex() {
+        // Set state.
+        mTabDragSource.setGlobalState(mTabBeingDragged);
+        // Simulate destination instance.
+        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(CURR_INSTANCE_ID + 1);
+        // Mock strip actions.
+        StripLayoutTab stripTab = mock(StripLayoutTab.class);
+        float tabWidth = 5f;
+        when(stripTab.getWidth()).thenReturn(tabWidth);
+        // Tab drop POS_X is at right half of tab.
+        when(stripTab.getDrawX()).thenReturn(POS_X - (tabWidth / 2 + 1));
+        when(stripTab.getId()).thenReturn(10);
+        when(mStripLayoutHelper.findIndexForTab(10)).thenReturn(2);
+        when(mStripLayoutHelper.getTabAtPosition(POS_X)).thenReturn(stripTab);
 
-        // Perform drag n drop simulation actions for movement outside the strip layout.
-        simulateDragDropEvents(/* withinStripLayout= */ false);
+        // Trigger drop in tab strip.
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DROP, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED, POS_X, mPosY);
 
-        // Verify Since the drop is outside the TabToolbar area the tab will be move to a new
-        // Chrome Window.
-        verify(mMultiInstanceManager, times(TAB_ID)).moveTabToNewWindow(mTabBeingDragged);
+        // Verify - Tab moved to destination window at index.
+        verify(mMultiInstanceManager, times(1)).moveTabToWindow(any(), eq(mTabBeingDragged), eq(3));
+        verify(mMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
     }
 
     @Test
-    public void test_clearActiveClickedTab_SimulateDragDrop_ReturnsSuccess() {
-        // Call startDrag to set class variables.
-        mTabDragSource.startTabDragAction(
-                mTabsToolbarView, mStripLayoutHelper, mTabBeingDragged, DRAG_START_POINT);
+    public void test_DropInDestinationTabStripOnNonTab_MoveTabToDestinationWindowAtEnd() {
+        // Set state.
+        mTabDragSource.setGlobalState(mTabBeingDragged);
+        // Simulate destination instance.
+        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(CURR_INSTANCE_ID + 1);
+        // Mock strip actions.
+        when(mStripLayoutHelper.getTabAtPosition(POS_X)).thenReturn(null);
+        when(mStripLayoutHelper.getTabCount()).thenReturn(10);
 
-        // Perform drag n drop simulation action.
-        simulateDragDropEvents(true);
+        // Trigger drop in tab strip.
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DROP, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED, POS_X, mPosY);
 
-        // Verify
-        verify(mStripLayoutHelper, times(TAB_ID)).clearActiveClickedTab();
-    }
-
-    // Simulates drag n drop action events moving within or outside the strip.
-    private void simulateDragDropEvents(boolean withinStripLayout) {
-        // Mock same source
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(CURR_INSTANCE_ID);
-
-        OnDragListener dragListener = mTabDragSource.getDragListener();
-        if (withinStripLayout) {
-            eventsWithinStripLayout(dragListener);
-        } else {
-            eventsOutsideStripLayout(dragListener);
-        }
-
-        if (withinStripLayout) {
-            dragListener.onDrag(
-                    mTabsToolbarView, createDragEvent(DragEvent.ACTION_DRAG_ENDED, 0f, 0f, 0));
-        } else {
-            dragListener.onDrag(
-                    mTabsToolbarView,
-                    createDragEvent(
-                            DragEvent.ACTION_DRAG_ENDED, DROP_X_SCREEN_POS, DROP_Y_SCREEN_POS, 0));
-        }
-    }
-
-    // Drag n drop action for moving within the tab strip.
-    private void eventsWithinStripLayout(OnDragListener onTabDragListener) {
-        // The tab movement is performed by generating the motion events as if the user is moving a
-        // tab. Within the tab strip movement is when horizontal/x position stays within the strip
-        // width and vertical/y stays within the strip height. The top left of the strip layout is
-        // (0f, 0f). For verification the y-offset is zero.
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_STARTED, TAB_STRIP_X_START, TAB_STRIP_Y_START, 0));
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_ENTERED, TAB_STRIP_X_START, TAB_STRIP_Y_START, 0));
-
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_LOCATION,
-                        TAB_STRIP_X_START + TAB_X_OFFSET,
-                        TAB_STRIP_Y_START,
-                        0));
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_LOCATION,
-                        TAB_STRIP_X_START + 2 * TAB_X_OFFSET,
-                        TAB_STRIP_Y_START,
-                        0));
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_LOCATION,
-                        TAB_STRIP_X_START + 3 * TAB_X_OFFSET,
-                        TAB_STRIP_Y_START,
-                        0));
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_LOCATION,
-                        TAB_STRIP_X_START + 4 * TAB_X_OFFSET,
-                        TAB_STRIP_Y_START,
-                        0));
-        // Total distance moved so far by pointer is still within the width of the tab strip.
-
-        onTabDragListener.onDrag(
-                mTabsToolbarView, createDragEvent(DragEvent.ACTION_DROP, 0f, 0f, 0));
-    }
-
-    // Drag n drop action for moving outside the tab strip.
-    private void eventsOutsideStripLayout(OnDragListener onTabDragListener) {
-        // The tab movement is performed by generating the motion events as if the user is moving a
-        // tab. Outside the tab strip movement here is when horizontal/x position stays constant
-        // within the strip width and vertical/y position goes beyond the strip height (below the
-        // strip in this case)). The top left of the strip layout is (0f, 0f). For verification the
-        // x-offset is zero.
-
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_STARTED, TAB_STRIP_X_START, TAB_STRIP_Y_START, 0));
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_ENTERED, TAB_STRIP_X_START, TAB_STRIP_Y_START, 0));
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_LOCATION,
-                        TAB_STRIP_X_START,
-                        TAB_STRIP_Y_START + TAB_STRIP_Y_STEP,
-                        0)); // Move within the tabs area.
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_LOCATION,
-                        TAB_STRIP_X_START,
-                        TAB_Y_OFFSET_WITHIN,
-                        0)); // Still within the tabs area.
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_LOCATION,
-                        TAB_STRIP_X_START,
-                        TAB_Y_OFFSET_OUTSIDE,
-                        0)); // Outside the tabs area but inside the toolbars.
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_LOCATION,
-                        TAB_STRIP_X_START,
-                        TAB_Y_OFFSET_WITHIN,
-                        0)); // Back to within the tabs area.
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_LOCATION,
-                        TAB_STRIP_X_START,
-                        TAB_STRIP_Y_START + TAB_Y_OFFSET_OUTSIDE,
-                        0));
-        // Total distance moved by pointer is beyond height of the tab strip.
-
-        // This event will indicate the the pointer has moved outside of the TabToolbar view and no
-        // more ACTION_DRAG_LOCATION and ACTION_DROP events will be received too.
-        onTabDragListener.onDrag(
-                mTabsToolbarView,
-                createDragEvent(
-                        DragEvent.ACTION_DRAG_EXITED,
-                        TAB_STRIP_X_START,
-                        TAB_STRIP_Y_START + TAB_Y_OFFSET_OUTSIDE,
-                        0));
-    }
-
-    /** Tests the instance of the local class {@link TabDragSource} get and clear methods. */
-    @Test
-    @Config(qualifiers = "sw600dp-w600dp")
-    public void test_canAcceptTabDrop_SimulateDragDrops_ReturnsSuccess() {
-
-        // Perform drag n drop simulation action as if dropped on another Chrome Window and verify.
-        // Drop event on another Chrome window in the in top half.
-        DragDropGlobalState.getInstance().dragSourceInstanceId = CURR_INSTANCE_ID;
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(ANOTHER_INSTANCE_ID);
-        TabDragSource.OnDragListenerImpl dragListener = mTabDragSource.getDragListener();
-        boolean res =
-                dragListener.canAcceptTabDrop(createDragEvent(DragEvent.ACTION_DROP, 150f, 10f, 0));
-        assertTrue("Tab drop should be accepted in another tabs toolbar view.", res);
-        assertTrue(
-                "After the drop event on top half accept next should be true.",
-                DragDropGlobalState.getInstance().acceptNextDrop);
-        DragDropGlobalState.getInstance().reset();
-
-        // Trigger another drop event on the same, not drag source, Chrome window in the bottom
-        // half.
-        DragDropGlobalState.getInstance().dragSourceInstanceId = CURR_INSTANCE_ID;
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(ANOTHER_INSTANCE_ID);
-        res = dragListener.canAcceptTabDrop(createDragEvent(DragEvent.ACTION_DROP, 150f, 45f, 0));
-        assertTrue(
-                "Tab drop should be accepted in the bottom half but the flag is set to"
-                        + " ignore drop.",
-                res);
-        assertFalse(
-                "After the drop event in the bottom half, the next accept should not be true.",
-                DragDropGlobalState.getInstance().acceptNextDrop);
-        DragDropGlobalState.getInstance().reset();
-
-        // Perform ACTION_DROP event on the same tabs view as the source view
-        DragDropGlobalState.getInstance().dragSourceInstanceId = CURR_INSTANCE_ID;
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(CURR_INSTANCE_ID);
-        res = dragListener.canAcceptTabDrop(createDragEvent(DragEvent.ACTION_DROP, 150f, 35f, 0));
-        assertFalse("Tab drop should be ignored if it is the same as drag source toolbar", res);
-        assertFalse(
-                "After the drop event in the bottom half, the next accept should not be true.",
-                DragDropGlobalState.getInstance().acceptNextDrop);
-        DragDropGlobalState.getInstance().reset();
+        // Verify - Tab moved to destination window at index.
+        verify(mMultiInstanceManager, times(1))
+                .moveTabToWindow(any(), eq(mTabBeingDragged), eq(10));
+        verify(mMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
     }
 
     @Test
-    public void test_sendPositionInfoToSysUI_WithNewWindowIsOpened_ReturnsSuccess() {
+    public void test_DropInDestination_WithIncorrectClipData_DoesNotMoveTab() {
         // Call startDrag to set class variables.
-        mTabDragSource.startTabDragAction(
-                mTabsToolbarView, mStripLayoutHelper, mTabBeingDragged, DRAG_START_POINT);
+        mTabDragSource.startTabDragAction(mTabsToolbarView, mTabBeingDragged, DRAG_START_POINT);
+        // Simulate destination instance.
+        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(CURR_INSTANCE_ID + 1);
 
-        // Perform drag n drop simulation actions for movement outside the strip layout.
-        simulateDragDropEvents(/* withinStripLayout= */ false);
+        // Trigger drop.
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DROP, POS_X, mPosY, TAB_ID_NOT_DRAGGED);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED, POS_X, mPosY);
 
-        // Verify Since the drop is outside the TabToolbar area verify new window opened
-        // TODO (crbug.com/1495815): check if intent is send to SysUI to position the window.
-        verify(mMultiInstanceManager).moveTabToNewWindow(mTabBeingDragged);
+        // Verify - Move to new window not invoked.
+        verify(mMultiInstanceManager, times(0)).moveTabToNewWindow(mTabBeingDragged);
+        verifyNoInteractions(mStripLayoutHelper);
     }
 
     @Test
@@ -490,8 +370,7 @@
         final float dragStartYPosition = 45f;
         final PointF dragStartPoint = new PointF(dragStartXPosition, dragStartYPosition);
         // Call startDrag to set class variables.
-        mTabDragSource.startTabDragAction(
-                mTabsToolbarView, mStripLayoutHelper, mTabBeingDragged, dragStartPoint);
+        mTabDragSource.startTabDragAction(mTabsToolbarView, mTabBeingDragged, dragStartPoint);
 
         View.DragShadowBuilder tabDragShadowBuilder =
                 mTabDragSource.createTabDragShadowBuilder(mActivity, true);
@@ -511,4 +390,35 @@
                 Math.round(dragStartYPosition),
                 dragAnchor.y);
     }
+
+    @Test
+    public void test_OnDragEndAfterExit_NewWindowIsOpened() {
+        // Call startDrag to set class variables.
+        mTabDragSource.startTabDragAction(mTabsToolbarView, mTabBeingDragged, DRAG_START_POINT);
+
+        // Perform drag n drop simulation actions for movement outside the strip layout.
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED, POS_X, mPosY);
+        triggerDragEvent(DragEvent.ACTION_DRAG_EXITED, DROP_X_SCREEN_POS, DROP_Y_SCREEN_POS);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED, DROP_X_SCREEN_POS, DROP_Y_SCREEN_POS);
+
+        // Verify Since the drop is outside the TabToolbar area verify new window opened
+        // TODO (crbug.com/1495815): check if intent is send to SysUI to position the window.
+        verify(mMultiInstanceManager).moveTabToNewWindow(mTabBeingDragged);
+    }
+
+    private void triggerDragEvent(int action, float x, float y) {
+        triggerDragEvent(action, x, y, TAB_ID);
+    }
+
+    private void triggerDragEvent(int action, float x, float y, int tabId) {
+        DragEvent event = mock(DragEvent.class);
+        when(event.getAction()).thenReturn(action);
+        when(event.getX()).thenReturn(x);
+        when(event.getY()).thenReturn(y);
+        when(event.getClipData())
+                .thenReturn(
+                        new ClipData(null, SUPPORTED_MIME_TYPES, new Item("TabId=" + tabId, null)));
+        mTabDragSource.onDrag(mTabsToolbarView, event);
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDropTargetTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDropTargetTest.java
deleted file mode 100644
index 622d765..0000000
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/TabDropTargetTest.java
+++ /dev/null
@@ -1,311 +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.
-
-package org.chromium.chrome.browser.compositor.overlays.strip;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Activity;
-import android.content.ClipData;
-import android.graphics.PointF;
-import android.view.View;
-
-import androidx.core.view.ContentInfoCompat;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-import org.robolectric.Robolectric;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.dragdrop.ChromeDragAndDropBrowserDelegate;
-import org.chromium.chrome.browser.dragdrop.DragDropGlobalState;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
-import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.tab.MockTab;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
-import org.chromium.ui.base.LocalizationUtils;
-
-/** Tests for {@link TabDropTarget}. */
-@RunWith(BaseRobolectricTestRunner.class)
-@EnableFeatures({ChromeFeatureList.TAB_DRAG_DROP_ANDROID})
-public class TabDropTargetTest {
-
-    private static final int CURRENT_INSTANCE_ID = 1;
-    private static final int ANOTHER_INSTANCE_ID = 2;
-    // clang-format on
-    @Rule public TestRule mFeaturesProcessorRule = new Features.JUnitProcessor();
-    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
-    @Mock private StripLayoutHelperManager mStripLayoutHelperManager;
-    @Mock private StripLayoutHelper mStripLayoutHelper;
-    @Mock private MultiInstanceManager mMultiInstanceManager;
-    @Mock private StripLayoutTab mStripLayoutTab;
-    @Mock private View mToolbarContainerView;
-    @Mock private Profile mProfile;
-    private TabDropTarget mTabDropTarget;
-    private Activity mActivity;
-    private ContentInfoCompat mContentInfoCompatPayload;
-
-    /** Resets the environment before each test. */
-    @Before
-    public void beforeTest() {
-        mActivity = Robolectric.setupActivity(Activity.class);
-        mActivity.setTheme(org.chromium.chrome.R.style.Theme_BrowserUI);
-
-        PriceTrackingFeatures.setPriceTrackingEnabledForTesting(false);
-
-        // Create both the TabDragSource and TabDropTarget.
-        mTabDropTarget =
-                new TabDropTarget(
-                        mStripLayoutHelperManager, mMultiInstanceManager, mToolbarContainerView);
-
-        // Mock the tab to be dragged.
-        DragDropGlobalState.getInstance().tabBeingDragged =
-                MockTab.createAndInitialize(5, mProfile);
-        DragDropGlobalState.getInstance().dragSourceInstanceId = CURRENT_INSTANCE_ID;
-
-        // Mock strip methods.
-        when(mStripLayoutHelperManager.getActiveStripLayoutHelper()).thenReturn(mStripLayoutHelper);
-
-        // Create drop payload from the drag source view.
-        mContentInfoCompatPayload =
-                new ContentInfoCompat.Builder(
-                                createClipData(
-                                        mToolbarContainerView,
-                                        DragDropGlobalState.getInstance().tabBeingDragged),
-                                ContentInfoCompat.SOURCE_DRAG_AND_DROP)
-                        .build();
-    }
-
-    @After
-    public void tearDown() {
-        mContentInfoCompatPayload = null;
-        mToolbarContainerView = null;
-        mTabDropTarget = null;
-        DragDropGlobalState.getInstance().reset();
-    }
-
-    private ClipData createClipData(View view, Tab tab) {
-        ClipData.Item item =
-                new ClipData.Item("TabId=" + (tab != null ? tab.getId() : Tab.INVALID_TAB_ID));
-        ClipData dragData =
-                new ClipData(
-                        (CharSequence) view.getTag(),
-                        ChromeDragAndDropBrowserDelegate.SUPPORTED_MIME_TYPES,
-                        item);
-        return dragData;
-    }
-
-    /**
-     * Tests the method {@link TabDropTarget#onReceiveContent(View, ContentInfoCompat)}. Checks that
-     * it successfully accepts a drop of a payload on different tabs layout view.
-     */
-    @Test
-    public void test_TabDropAction_onDifferentTabsLayout_ReturnsSuccess() {
-        // Mock different tab layout.
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(ANOTHER_INSTANCE_ID);
-
-        DragDropGlobalState.getInstance().acceptNextDrop = true;
-        DragDropGlobalState.getInstance().dropLocation = new PointF(100f, 20.f);
-        Mockito.doNothing()
-                .when(mMultiInstanceManager)
-                .moveTabToWindow(
-                        any(), eq(DragDropGlobalState.getInstance().tabBeingDragged), anyInt());
-
-        // Perform action of a simulated drop of ClipData payload on drop target view.
-        ContentInfoCompat remainingPayload =
-                mTabDropTarget.onReceiveContent(mToolbarContainerView, mContentInfoCompatPayload);
-
-        // Verify the ClipData dropped was consumed.
-        assertNull("Remaining payload should be null.", remainingPayload);
-    }
-
-    /**
-     * Tests the method {@link TabDropTarget#onReceiveContent(View, ContentInfoCompat)}. Checks that
-     * it successfully rejects the drop of payload on same tab layout view.
-     */
-    @Test
-    public void test_TabDropAction_onSameTabsLayout_ReturnsFalse() {
-        // Mock same tab layout.
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(CURRENT_INSTANCE_ID);
-
-        DragDropGlobalState.getInstance().acceptNextDrop = true;
-
-        // Perform action of a simulated drop of ClipData payload on drag source view.
-        ContentInfoCompat remainingPayload =
-                mTabDropTarget.onReceiveContent(mToolbarContainerView, mContentInfoCompatPayload);
-
-        // Verify the drop was not consumed, the original payload was returned.
-        assertEquals(
-                "Original payload should be returned.",
-                mContentInfoCompatPayload,
-                remainingPayload);
-    }
-
-    @Test
-    public void test_TabDropAction_doNotAcceptNextDrop_ReturnsFalse() {
-        // Mock different tab layout.
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(ANOTHER_INSTANCE_ID);
-        DragDropGlobalState.getInstance().acceptNextDrop = false;
-
-        // Perform action of a simulated drop of ClipData payload on drag source view.
-        ContentInfoCompat remainingPayload =
-                mTabDropTarget.onReceiveContent(mToolbarContainerView, mContentInfoCompatPayload);
-
-        // Verify the drop was not consumed, the original payload was returned.
-        assertEquals(
-                "Original payload should be returned.",
-                mContentInfoCompatPayload,
-                remainingPayload);
-    }
-
-    /**
-     * Tests the method {@link TabDropTarget#onReceiveContent(View, ContentInfoCompat)}. Checks that
-     * it rejects a ClipData drop of a payload with the tabId that does not match with the saved Tab
-     * info even if the source and destination windows are different.
-     */
-    @Test
-    public void test_TabDropAction_onDifferentTabsLayout_WithBadClipDataDrop_ReturnsFailure() {
-        // Mock different tab layout.
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(ANOTHER_INSTANCE_ID);
-
-        DragDropGlobalState.getInstance().acceptNextDrop = true;
-        DragDropGlobalState.getInstance().dropLocation = new PointF(100f, 20.f);
-
-        // Create ClipData for non-Chrome apps with invalid tab id.
-        Tab tabForBadClipData = MockTab.createAndInitialize(555, mProfile);
-        ContentInfoCompat compactPayloadWithBadClipData =
-                new ContentInfoCompat.Builder(
-                                createClipData(mToolbarContainerView, tabForBadClipData),
-                                ContentInfoCompat.SOURCE_DRAG_AND_DROP)
-                        .build();
-
-        // Perform action of a simulated drop of ClipData payload on drop target view.
-        ContentInfoCompat remainingPayload =
-                mTabDropTarget.onReceiveContent(
-                        mToolbarContainerView, compactPayloadWithBadClipData);
-
-        // Verify the ClipData dropped was rejected.
-        assertEquals(
-                "Original payload should be returned.",
-                compactPayloadWithBadClipData,
-                remainingPayload);
-    }
-
-    @Test
-    public void test_TabDropAction_onDifferentTabsLayout_WithNullClipDataDrop_ReturnsFailure() {
-        // Mock different tab layout.
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(ANOTHER_INSTANCE_ID);
-
-        DragDropGlobalState.getInstance().acceptNextDrop = true;
-
-        // Perform action of a simulated drop of null payload on drop target view.
-        ContentInfoCompat remainingPayload =
-                mTabDropTarget.onReceiveContent(mToolbarContainerView, null);
-
-        // Verify the ClipData dropped was rejected.
-        assertNull("Dropped null payload should be returned.", remainingPayload);
-    }
-
-    @Test
-    public void test_TabDropAction_onDifferentTabsLayout_WithEmptyClipDataDrop_ReturnsFailure() {
-        // Mock different tab layout.
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(ANOTHER_INSTANCE_ID);
-
-        DragDropGlobalState.getInstance().acceptNextDrop = true;
-
-        // Create empty ClipData.
-        ContentInfoCompat compactPayloadWithEmptyClipData =
-                new ContentInfoCompat.Builder(
-                                createClipData(mToolbarContainerView, null),
-                                ContentInfoCompat.SOURCE_DRAG_AND_DROP)
-                        .build();
-
-        // Perform action of a simulated drop of empty ClipData payload on drop target view.
-        ContentInfoCompat remainingPayload =
-                mTabDropTarget.onReceiveContent(
-                        mToolbarContainerView, compactPayloadWithEmptyClipData);
-
-        // Verify the ClipData dropped was rejected.
-        assertEquals(
-                "Dropped same payload should be returned.",
-                compactPayloadWithEmptyClipData,
-                remainingPayload);
-    }
-    @Test
-    public void test_TabDropAction_onDifferentTabsLayout_onTab_inRTLLayout_success() {
-        LocalizationUtils.setRtlForTesting(true);
-        // Simulate different tab layout.
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(ANOTHER_INSTANCE_ID);
-
-        // Drop point is on the left side of the tab.
-        final int droppedOnIndex = 1;
-        prepareDropOnTab(3, droppedOnIndex, 1, new PointF(140.f, 20.f), 101.f, 100.f);
-
-        // Perform action of a simulated drop of ClipData payload on a desired tab of drop target
-        // view.
-        ContentInfoCompat remainingPayload =
-                mTabDropTarget.onReceiveContent(mToolbarContainerView, mContentInfoCompatPayload);
-
-        // Verify the ClipData dropped was consumed and the tab is positioned on a desired location.
-        assertNull("Dropped payload should be fully consumed.", remainingPayload);
-        verify(mStripLayoutHelper, times(1)).selectTabAtIndex(droppedOnIndex + 1);
-    }
-
-    @Test
-    public void test_TabDropAction_onDifferentTabsLayout_onTab_inLTRLayout_success() {
-        LocalizationUtils.setRtlForTesting(false);
-        // Simulate different tab layout.
-        when(mMultiInstanceManager.getCurrentInstanceId()).thenReturn(ANOTHER_INSTANCE_ID);
-
-        // Drop point is on the right side of the tab.
-        final int droppedOnIndex = 1;
-        prepareDropOnTab(3, droppedOnIndex, 1, new PointF(160.f, 20.f), 101.f, 100.f);
-
-        // Perform action of a simulated drop of ClipData payload on a desired tab of drop target
-        // view.
-        ContentInfoCompat remainingPayload =
-                mTabDropTarget.onReceiveContent(mToolbarContainerView, mContentInfoCompatPayload);
-
-        // Verify the ClipData dropped was consumed and the inset tab is desired location.
-        assertNull("Dropped payload should be fully consumed.", remainingPayload);
-        verify(mStripLayoutHelper, times(1)).selectTabAtIndex(droppedOnIndex + 1);
-    }
-
-    private void prepareDropOnTab(
-            int tabsCount,
-            int droppedOnIndex,
-            int droppedOnTabId,
-            PointF dropPoint,
-            float tabStartX,
-            float tabWidth) {
-        DragDropGlobalState.getInstance().acceptNextDrop = true;
-        DragDropGlobalState.getInstance().dropLocation = dropPoint;
-        when(mStripLayoutHelper.getTabAtPosition(anyFloat())).thenReturn(mStripLayoutTab);
-        when(mStripLayoutHelper.getTabCount()).thenReturn(tabsCount);
-        when(mStripLayoutHelper.findIndexForTab(droppedOnTabId)).thenReturn(droppedOnIndex);
-        when(mStripLayoutTab.getId()).thenReturn(droppedOnTabId);
-        when(mStripLayoutTab.getDrawX()).thenReturn(tabStartX);
-        when(mStripLayoutTab.getWidth()).thenReturn(tabWidth);
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedActionDelegateImplTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedActionDelegateImplTest.java
index bdd71b8..0b406fc4 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedActionDelegateImplTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedActionDelegateImplTest.java
@@ -34,6 +34,7 @@
 import org.chromium.chrome.browser.feed.webfeed.WebFeedBridgeJni;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.SyncConsentActivityLauncherImpl;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
@@ -60,6 +61,8 @@
 
     @Mock private TabModelSelector mTabModelSelector;
 
+    @Mock private Profile mProfile;
+
     @Captor ArgumentCaptor<Intent> mIntentCaptor;
 
     private FeedActionDelegateImpl mFeedActionDelegateImpl;
@@ -76,7 +79,8 @@
                         mMockNavigationDelegate,
                         mMockBookmarkModel,
                         BrowserUiUtils.HostSurface.NOT_SET,
-                        mTabModelSelector);
+                        mTabModelSelector,
+                        mProfile);
         jniMocker.mock(WebFeedBridgeJni.TEST_HOOKS, mWebFeedBridgeJniMock);
 
         when(mWebFeedBridgeJniMock.isCormorantEnabledForLocale()).thenReturn(true);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/read_later/ReadingListUtilsUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/read_later/ReadingListUtilsUnitTest.java
index 42a07e4..95de21c 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/read_later/ReadingListUtilsUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/read_later/ReadingListUtilsUnitTest.java
@@ -24,6 +24,7 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.bookmarks.BookmarkModel;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.components.bookmarks.BookmarkId;
@@ -130,7 +131,8 @@
                         Mockito.mock(BottomSheetController.class),
                         Mockito.mock(BookmarkModel.class),
                         /* bookmarkId= */ null,
-                        BookmarkType.READING_LIST));
+                        BookmarkType.READING_LIST,
+                        Mockito.mock(Profile.class)));
 
         doReturn(BookmarkType.READING_LIST).when(bookmarkId).getType();
         Assert.assertFalse(
@@ -139,7 +141,8 @@
                         Mockito.mock(BottomSheetController.class),
                         Mockito.mock(BookmarkModel.class),
                         bookmarkId,
-                        BookmarkType.READING_LIST));
+                        BookmarkType.READING_LIST,
+                        Mockito.mock(Profile.class)));
 
         doReturn(BookmarkType.NORMAL).when(bookmarkId).getType();
         Assert.assertFalse(
@@ -148,7 +151,8 @@
                         Mockito.mock(BottomSheetController.class),
                         Mockito.mock(BookmarkModel.class),
                         bookmarkId,
-                        BookmarkType.NORMAL));
+                        BookmarkType.NORMAL,
+                        Mockito.mock(Profile.class)));
     }
 
     @Test
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 123746b..338407a 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -1689,6 +1689,9 @@
   <message name="IDS_LOGIN_GENERIC_RETRY_BUTTON" desc="Label for the button to retry operation during login process.">
     Retry
   </message>
+  <message name="IDS_LOGIN_GENERIC_DONE_BUTTON" desc="Label for the button on the information dialogs to close the dialog and proceed as expected.">
+    Done
+  </message>
   <message name="IDS_LOGIN_SAML_NOTICE_SHORT" desc="Text message displayed above SAML portal to early indicate that the user is being redirected to another sign-in provider. This is the version of the string used in the GAIA flow.">
     Sign-in hosted by <ph name="SAML_DOMAIN">$1<ex>saml.com</ex></ph>
   </message>
@@ -1713,13 +1716,13 @@
   <message name="IDS_LOGIN_LOCAL_PASSWORD_RESET_TITLE" desc="Title for the local password reset dialog.">
     Reset <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph> password
   </message>
-  <message name="IDS_LOGIN_LOCAL_PASSWORD_SETUP_DONE_TITLE" desc="Title for the local password setup done dialog.">
+  <message name="IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_SET_TITLE" desc="Title for the factor setup informational dialog to show that the local password was set successfully.">
     Your password is set
   </message>
-  <message name="IDS_LOGIN_LOCAL_PASSWORD_RESET_DONE_TITLE" desc="Title for the local password reset done dialog.">
+  <message name="IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_UPDATED_TITLE" desc="Title for the factor setup informational dialog to show that local password was updated successfuly.">
     Your password has been updated
   </message>
-  <message name="IDS_LOGIN_LOCAL_PASSWORD_SETUP_DONE_SUBTITLE" desc="Subitle for the local password setup done dialog.">
+  <message name="IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_SUBTITLE" desc="Subtitle for the factor setup informational dialog, shown when local password was set up (to indicate that it only applies to the current device).">
     You can use this password to sign in to your <ph name="DEVICE_TYPE">$1<ex>Chromebook</ex></ph>.
   </message>
   <message name="IDS_LOGIN_MANUAL_PASSWORD_TITLE" desc="Title for the manual password setting dialog.">
@@ -7305,6 +7308,12 @@
   <message name="IDS_FLOATING_WORKSPACE_NO_NETWORK_BUTTON" desc="Button to show network settings for Floating Workspace notification when is no network connection.">
     Network settings
   </message>
+ <message name="IDS_FLOATING_WORKSPACE_SAFE_MODE_TITLE" desc="Title of Floating Workspace notification when ChromeOS is in safe mode." translateable="false">
+    Floating Workspace disabled
+  </message>
+  <message name="IDS_FLOATING_WORKSPACE_SAFE_MODE_MESSAGE" desc="Message for Floating Workspace notification when ChromeOS is in safe mode." translateable="false">
+    ChromeOS in safe mode because too many consecutive crashes, Floating Workspace is disabled.
+  </message>
   <message name="IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_TITLE" desc="Title of Floating Workspace notification asking user to restore or not when a new remote desk arrives after an earlier restoration error occurs.">
     Resume previous session?
   </message>
diff --git a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_LOCAL_PASSWORD_SETUP_DONE_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_SET_TITLE.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_LOGIN_LOCAL_PASSWORD_SETUP_DONE_TITLE.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_SET_TITLE.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_LOCAL_PASSWORD_SETUP_DONE_SUBTITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_SUBTITLE.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_LOGIN_LOCAL_PASSWORD_SETUP_DONE_SUBTITLE.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_SUBTITLE.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_LOCAL_PASSWORD_RESET_DONE_TITLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_UPDATED_TITLE.png.sha1
similarity index 100%
rename from chrome/app/chromeos_strings_grdp/IDS_LOGIN_LOCAL_PASSWORD_RESET_DONE_TITLE.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_UPDATED_TITLE.png.sha1
diff --git a/chrome/app/chromeos_strings_grdp/IDS_LOGIN_GENERIC_DONE_BUTTON.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_GENERIC_DONE_BUTTON.png.sha1
new file mode 100644
index 0000000..896f30f
--- /dev/null
+++ b/chrome/app/chromeos_strings_grdp/IDS_LOGIN_GENERIC_DONE_BUTTON.png.sha1
@@ -0,0 +1 @@
+af5d4bff240d254ee0db10275168a5abc08d96ae
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 480c6ec..37c654c 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -6289,6 +6289,9 @@
   <message name="IDS_OS_SETTINGS_PRIVACY_HUB_NO_APP_CAN_USE_MIC_TEXT" translateable="false" desc="The text displayed in the Apps section of the microphone subpage when microphone is not allowed.">
     No app is allowed to use your microphone
   </message>
+  <message name="IDS_OS_SETTINGS_PRIVACY_HUB_BLOCKED_FOR_ALL_TEXT" translateable="false" desc="The text displayed as a sublabel in privacy hub page and in sensor subpages when sensor access is disabled.">
+    Blocked for all
+  </message>
   <message name="IDS_OS_SETTINGS_REVAMP_SECURE_DNS" desc="Text for secure DNS toggle in Privacy options for ChromeOS">
     Encrypt URLs entered into the browser
   </message>
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index 00f7743..7056e4db 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -274,6 +274,7 @@
     "webauthn/passkey_phone_dark.icon",
     "webauthn/passkey_usb.icon",
     "webauthn/passkey_usb_dark.icon",
+    "webauthn/usb_security_key.icon",
     "webauthn/webauthn_error.icon",
     "webauthn/webauthn_error_dark.icon",
     "zoom_in.icon",
diff --git a/chrome/app/vector_icons/webauthn/usb_security_key.icon b/chrome/app/vector_icons/webauthn/usb_security_key.icon
new file mode 100644
index 0000000..f02adc6
--- /dev/null
+++ b/chrome/app/vector_icons/webauthn/usb_security_key.icon
@@ -0,0 +1,31 @@
+// 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.
+
+CANVAS_DIMENSIONS, 24,
+MOVE_TO, 9.5f, 9.5f,
+R_ARC_TO, 2.5f, 2.5f, 0, 1, 1, 5, 0,
+R_ARC_TO, 2.5f, 2.5f, 0, 0, 1, -5, 0,
+CLOSE,
+MOVE_TO, 16, 3,
+R_V_LINE_TO, 12.5f,
+H_LINE_TO, 8,
+LINE_TO, 8, 3,
+H_LINE_TO, 16,
+CLOSE,
+MOVE_TO, 7.5f, 17.5f,
+H_LINE_TO, 8,
+R_V_LINE_TO, 4,
+R_CUBIC_TO, 0, 0.83f, 0.67f, 1.5f, 1.5f, 1.5f,
+R_H_LINE_TO, 5,
+R_CUBIC_TO, 0.83f, 0, 1.5f, -0.67f, 1.5f, -1.5f,
+R_V_LINE_TO, -4,
+R_H_LINE_TO, 0.5f,
+R_CUBIC_TO, 0.83f, 0, 1.5f, -0.67f, 1.5f, -1.5f,
+V_LINE_TO, 3,
+R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
+H_LINE_TO, 8,
+R_CUBIC_TO, -1.1f, 0, -2, 0.9f, -2, 2,
+R_V_LINE_TO, 13,
+R_CUBIC_TO, 0, 0.83f, 0.67f, 1.5f, 1.5f, 1.5f,
+CLOSE
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 6edf8ac9..ad1b35f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1442,6 +1442,7 @@
 const FeatureEntry::FeatureParam kShortcutBoostMultipleSearchesAndUrls[] = {
     {"ShortcutBoostSearchScore", "1414"},
     {"ShortcutBoostNonTopHitThreshold", "2"},
+    {"ShortcutBoostNonTopHitSearchThreshold", "3"},
     {"ShortcutBoostGroupWithSearches", "true"},
 };
 
@@ -11184,7 +11185,7 @@
     {"enable-web-app-system-media-controls-win",
      flag_descriptions::kWebAppSystemMediaControlsWinName,
      flag_descriptions::kWebAppSystemMediaControlsWinDescription, kOsWin,
-     FEATURE_VALUE_TYPE(webapps::features::kWebAppSystemMediaControlsWin)},
+     FEATURE_VALUE_TYPE(features::kWebAppSystemMediaControlsWin)},
 #endif  // BUILDFLAG(IS_WIN)
 
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 1a0a544a..1069289 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1903,6 +1903,8 @@
     "login/screens/osauth/cryptohome_recovery_screen.h",
     "login/screens/osauth/cryptohome_recovery_setup_screen.cc",
     "login/screens/osauth/cryptohome_recovery_setup_screen.h",
+    "login/screens/osauth/factor_setup_success_screen.cc",
+    "login/screens/osauth/factor_setup_success_screen.h",
     "login/screens/osauth/gaia_password_changed_screen.cc",
     "login/screens/osauth/gaia_password_changed_screen.h",
     "login/screens/osauth/gaia_password_changed_screen_legacy.cc",
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service.cc b/chrome/browser/ash/floating_workspace/floating_workspace_service.cc
index ff6b93dd..5505f31 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_service.cc
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_service.cc
@@ -64,6 +64,7 @@
     "notification_restore_after_error";
 constexpr char kNotificationForProgressStatus[] =
     "notification_progress_status";
+constexpr char kSafeMode[] = "notification_safe_mode";
 // Default time without activity after which a floating workspace template is
 // considered stale and becomes a candidate for garbage collection.
 constexpr base::TimeDelta kStaleFWSThreshold = base::Days(30);
@@ -87,6 +88,9 @@
   if (id == kNotificationForProgressStatus) {
     return FloatingWorkspaceServiceNotificationType::kProgressStatus;
   }
+  if (id == kSafeMode) {
+    return FloatingWorkspaceServiceNotificationType::kSafeMode;
+  }
   return FloatingWorkspaceServiceNotificationType::kUnknown;
 }
 
@@ -277,10 +281,9 @@
   switch (GetNotificationTypeById(notification_->id())) {
     case FloatingWorkspaceServiceNotificationType::kUnknown:
       // For unknown type of notification id, do nothing and run close logic.
-      break;
     case FloatingWorkspaceServiceNotificationType::kSyncErrorOrTimeOut:
-      break;
     case FloatingWorkspaceServiceNotificationType::kProgressStatus:
+    case FloatingWorkspaceServiceNotificationType::kSafeMode:
       break;
     case FloatingWorkspaceServiceNotificationType::kNoNetworkConnection:
       if (button_index.has_value()) {
@@ -332,6 +335,12 @@
 void FloatingWorkspaceService::InitForV2(
     syncer::SyncService* sync_service,
     desks_storage::DeskSyncService* desk_sync_service) {
+  // Disable floating workspace action in safe mode.
+  if (floating_workspace_util::IsSafeMode()) {
+    LOG(WARNING) << "Floating workspace disabled in safe mode.";
+    SendNotification(kSafeMode);
+    return;
+  }
   floating_workspace_metrics_util::
       RecordFloatingWorkspaceV2InitializedHistogram();
   SetUpServiceAndObservers(sync_service, desk_sync_service);
@@ -764,7 +773,7 @@
       notification_data.buttons.emplace_back(l10n_util::GetStringUTF16(
           IDS_FLOATING_WORKSPACE_RESTORE_FROM_ERROR_RESTORATION_BUTTON));
       break;
-    case ash::FloatingWorkspaceServiceNotificationType::kProgressStatus:
+    case FloatingWorkspaceServiceNotificationType::kProgressStatus:
       title =
           l10n_util::GetStringUTF16(IDS_FLOATING_WORKSPACE_PROGRESS_BAR_TITLE);
       notification_data.progress_status = l10n_util::GetStringUTF16(
@@ -778,6 +787,12 @@
                       .Get());
       is_progress_bar = true;
       break;
+    case FloatingWorkspaceServiceNotificationType::kSafeMode:
+      title = l10n_util::GetStringUTF16(IDS_FLOATING_WORKSPACE_SAFE_MODE_TITLE);
+      message =
+          l10n_util::GetStringUTF16(IDS_FLOATING_WORKSPACE_SAFE_MODE_MESSAGE);
+      warning_level = message_center::SystemNotificationWarningLevel::WARNING;
+      break;
     case FloatingWorkspaceServiceNotificationType::kUnknown:
       VLOG(2) << "Unknown notification type for floating workspace, skip "
                  "sending notification";
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_service.h b/chrome/browser/ash/floating_workspace/floating_workspace_service.h
index 9239cfa..cc659c8 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_service.h
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_service.h
@@ -56,6 +56,7 @@
   kSyncErrorOrTimeOut,
   kRestoreAfterError,
   kProgressStatus,
+  kSafeMode
 };
 
 // A keyed service to support floating workspace. Note that a periodical
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_util.cc b/chrome/browser/ash/floating_workspace/floating_workspace_util.cc
index 52686e9..01f33df 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_util.cc
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_util.cc
@@ -6,7 +6,9 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
+#include "ash/constants/ash_switches.h"
 #include "base/check.h"
+#include "base/command_line.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
@@ -24,8 +26,9 @@
 
 PrefService* GetActiveUserPrefService() {
   auto* active_user = user_manager::UserManager::Get()->GetActiveUser();
-  auto* user_profile = ProfileHelper::Get()->GetProfileByUser(active_user);
-  return user_profile->GetPrefs();
+  return active_user
+             ? ProfileHelper::Get()->GetProfileByUser(active_user)->GetPrefs()
+             : nullptr;
 }
 
 }  // namespace
@@ -44,8 +47,9 @@
 
 bool IsFloatingWorkspaceV2Enabled() {
   PrefService* pref_service = GetActiveUserPrefService();
-  DCHECK(pref_service);
-
+  if (!pref_service) {
+    return false;
+  }
   const PrefService::Preference* floating_workspace_pref =
       pref_service->FindPreference(prefs::kFloatingWorkspaceEnabled);
 
@@ -55,6 +59,7 @@
     // If there is a policy managing the pref, return what is set by policy.
     return pref_service->GetBoolean(prefs::kFloatingWorkspaceEnabled);
   }
+
   // TODO(b/297795546): Remove external ash feature flag.
   return features::IsFloatingWorkspaceV2Enabled();
 }
@@ -65,5 +70,14 @@
          nsh->ConnectedNetworkByType(NetworkTypePattern::Default()) != nullptr;
 }
 
+bool IsSafeMode() {
+  return base::CommandLine::ForCurrentProcess()->HasSwitch(
+      ash::switches::kSafeMode);
+}
+
+bool ShouldHandleRestartRestore() {
+  return IsFloatingWorkspaceV2Enabled() && !IsSafeMode();
+}
+
 }  // namespace floating_workspace_util
 }  // namespace ash
diff --git a/chrome/browser/ash/floating_workspace/floating_workspace_util.h b/chrome/browser/ash/floating_workspace/floating_workspace_util.h
index eddf004..2a880fa8 100644
--- a/chrome/browser/ash/floating_workspace/floating_workspace_util.h
+++ b/chrome/browser/ash/floating_workspace/floating_workspace_util.h
@@ -33,6 +33,9 @@
 
 bool IsInternetConnected();
 
+bool IsSafeMode();
+
+bool ShouldHandleRestartRestore();
 }  // namespace ash::floating_workspace_util
 
 #endif  // CHROME_BROWSER_ASH_FLOATING_WORKSPACE_FLOATING_WORKSPACE_UTIL_H_
diff --git a/chrome/browser/ash/login/debug_overlay_browsertest.cc b/chrome/browser/ash/login/debug_overlay_browsertest.cc
index 02bcc55..6428ed2 100644
--- a/chrome/browser/ash/login/debug_overlay_browsertest.cc
+++ b/chrome/browser/ash/login/debug_overlay_browsertest.cc
@@ -21,8 +21,14 @@
 constexpr char kDebugOverlay[] = "debuggerOverlay";
 constexpr char kScreensPanel[] = "DebuggerPanelScreens";
 
-constexpr int kOobeScreensCount = 53;
-constexpr int kLoginScreensCount = 49;
+constexpr int kCommonScreensCount = 43;
+constexpr int kOobeOnlyScreensCount = 11;
+constexpr int kLoginOnlyScreensCount = 7;
+
+constexpr int kOobeScreensCount = kCommonScreensCount + kOobeOnlyScreensCount;
+constexpr int kLoginScreensCount = kCommonScreensCount + kLoginOnlyScreensCount;
+
+// Feature-specific screens:
 constexpr int kOsInstallScreensCount = 2;
 constexpr int kLocalPasswordScreenCount = 3;
 
diff --git a/chrome/browser/ash/login/existing_user_controller.cc b/chrome/browser/ash/login/existing_user_controller.cc
index 32d3bd9..15021a5 100644
--- a/chrome/browser/ash/login/existing_user_controller.cc
+++ b/chrome/browser/ash/login/existing_user_controller.cc
@@ -1568,9 +1568,7 @@
   std::string device_id = known_user.GetDeviceId(user_context.GetAccountId());
   if (device_id.empty()) {
     const bool is_ephemeral = ChromeUserManager::Get()->IsEphemeralAccountId(
-                                  user_context.GetAccountId()) &&
-                              user_context.GetAccountId() !=
-                                  ChromeUserManager::Get()->GetOwnerAccountId();
+        user_context.GetAccountId());
     device_id = GenerateSigninScopedDeviceId(is_ephemeral);
   }
   user_context.SetDeviceId(device_id);
diff --git a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.cc b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.cc
index 0b81713a..59f8ec4c 100644
--- a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.cc
@@ -151,11 +151,7 @@
                                      Step::ADVERTISING_WITH_QR_CODE};
   CHECK(base::Contains(kPossibleSteps, status_.step));
 
-  pin_ = pin;
-  status_.step = Step::PIN_VERIFICATION;
-  status_.pin = pin_;
-  status_.payload.emplace<absl::monostate>();
-  NotifyObservers();
+  UpdateStatus(/*step=*/Step::PIN_VERIFICATION, /*payload=*/pin);
 }
 
 void TargetDeviceBootstrapController::OnConnectionAuthenticated(
@@ -170,10 +166,9 @@
 }
 
 void TargetDeviceBootstrapController::OnConnectionRejected() {
-  status_.step = Step::ERROR;
-  status_.payload = ErrorCode::CONNECTION_REJECTED;
+  UpdateStatus(/*step=*/Step::ERROR,
+               /*payload=*/ErrorCode::CONNECTION_REJECTED);
   CleanupIfNeeded();
-  NotifyObservers();
 }
 
 void TargetDeviceBootstrapController::OnConnectionClosed(
@@ -183,11 +178,10 @@
         /*succeeded=*/false, /*failure_reason=*/QuickStartMetrics::
             WifiTransferResultFailureReason::kConnectionDroppedDuringAttempt);
   }
-  status_.step = Step::ERROR;
-  status_.payload = ErrorCode::CONNECTION_CLOSED;
+
+  UpdateStatus(/*step=*/Step::ERROR, /*payload=*/ErrorCode::CONNECTION_CLOSED);
   authenticated_connection_.reset();
   CleanupIfNeeded();
-  NotifyObservers();
 }
 
 std::string TargetDeviceBootstrapController::GetDiscoverableName() {
@@ -196,6 +190,16 @@
   return device_type + " (" + code + ")";
 }
 
+void TargetDeviceBootstrapController::UpdateStatus(Step step, Payload payload) {
+  if (status_.step == step) {
+    return;
+  }
+
+  status_.step = step;
+  status_.payload = payload;
+  NotifyObservers();
+}
+
 void TargetDeviceBootstrapController::NotifyObservers() {
   QS_LOG(INFO)
       << "Notifying observers that the status has changed. New status step: "
@@ -212,17 +216,14 @@
   if (success) {
     return;
   }
-  status_.step = Step::ERROR;
-  status_.payload = ErrorCode::START_ADVERTISING_FAILED;
+  UpdateStatus(/*step=*/Step::ERROR,
+               /*payload=*/ErrorCode::START_ADVERTISING_FAILED);
   CleanupIfNeeded();
-  NotifyObservers();
 }
 
 void TargetDeviceBootstrapController::OnStopAdvertising() {
-  status_.step = Step::NONE;
-  status_.payload.emplace<absl::monostate>();
+  UpdateStatus(/*step=*/Step::NONE, /*payload=*/absl::monostate());
   CleanupIfNeeded();
-  NotifyObservers();
 }
 
 void TargetDeviceBootstrapController::OnNotifySourceOfUpdateResponse(
@@ -264,21 +265,17 @@
   if (!user_verification_response.has_value() ||
       user_verification_response->result ==
           mojom::UserVerificationResult::kUserNotVerified) {
-    status_.step = Step::ERROR;
-    status_.payload = ErrorCode::USER_VERIFICATION_FAILED;
-    NotifyObservers();
+    UpdateStatus(/*step=*/Step::ERROR,
+                 /*payload=*/ErrorCode::USER_VERIFICATION_FAILED);
     return;
   }
 
-  status_.step = Step::CONNECTED;
-  status_.payload.emplace<absl::monostate>();
-  NotifyObservers();
+  UpdateStatus(/*step=*/Step::CONNECTED, /*payload=*/absl::monostate());
 }
 
 void TargetDeviceBootstrapController::AttemptWifiCredentialTransfer() {
-  status_.step = Step::REQUESTING_WIFI_CREDENTIALS;
-  status_.payload.emplace<absl::monostate>();
-  NotifyObservers();
+  UpdateStatus(/*step=*/Step::REQUESTING_WIFI_CREDENTIALS,
+               /*payload=*/absl::monostate());
 
   authenticated_connection_->RequestWifiCredentials(base::BindOnce(
       &TargetDeviceBootstrapController::OnWifiCredentialsReceived,
@@ -290,15 +287,13 @@
   CHECK_EQ(status_.step, Step::REQUESTING_WIFI_CREDENTIALS);
 
   if (credentials.has_value()) {
-    status_.step = Step::WIFI_CREDENTIALS_RECEIVED;
-    status_.wifi_credentials = credentials.value();
+    UpdateStatus(/*step=*/Step::WIFI_CREDENTIALS_RECEIVED,
+                 /*payload=*/credentials.value());
   } else {
-    status_.step = Step::EMPTY_WIFI_CREDENTIALS_RECEIVED;
+    UpdateStatus(/*step=*/Step::EMPTY_WIFI_CREDENTIALS_RECEIVED,
+                 /*payload=*/absl::monostate());
   }
 
-  status_.payload.emplace<absl::monostate>();
-  NotifyObservers();
-
   // Record successful wifi credentials transfer. Failures will be
   // logged from the QuickStartDecoder class.
   QuickStartMetrics::RecordWifiTransferResult(
@@ -313,9 +308,8 @@
 void TargetDeviceBootstrapController::RequestGoogleAccountInfo() {
   CHECK(authenticated_connection_);
 
-  status_.step = Step::REQUESTING_GOOGLE_ACCOUNT_INFO;
-  status_.payload.emplace<absl::monostate>();
-  NotifyObservers();
+  UpdateStatus(/*step=*/Step::REQUESTING_GOOGLE_ACCOUNT_INFO,
+               /*payload=*/absl::monostate());
 
   authenticated_connection_->RequestAccountInfo(base::BindOnce(
       &TargetDeviceBootstrapController::OnGoogleAccountInfoReceived,
@@ -323,17 +317,15 @@
 }
 
 void TargetDeviceBootstrapController::OnGoogleAccountInfoReceived() {
-  status_.step = Step::GOOGLE_ACCOUNT_INFO_RECEIVED;
-  status_.payload.emplace<absl::monostate>();
-  NotifyObservers();
+  UpdateStatus(/*step=*/Step::GOOGLE_ACCOUNT_INFO_RECEIVED,
+               /*payload=*/absl::monostate());
 }
 
 void TargetDeviceBootstrapController::AttemptGoogleAccountTransfer() {
   CHECK(authenticated_connection_);
 
-  status_.step = Step::TRANSFERRING_GOOGLE_ACCOUNT_DETAILS;
-  status_.payload.emplace<absl::monostate>();
-  NotifyObservers();
+  UpdateStatus(/*step=*/Step::TRANSFERRING_GOOGLE_ACCOUNT_DETAILS,
+               /*payload=*/absl::monostate());
 
   // Request the challenge bytes from Gaia to be sent to the phone.
   CHECK(auth_broker_);
@@ -352,10 +344,9 @@
   if (!challenge.has_value()) {
     quick_start::QS_LOG(ERROR) << "Error fetching challenge bytes from Gaia. "
                                << "Reason: " << challenge.error().ToString();
-    status_.step = Step::ERROR;
-    status_.payload = ErrorCode::FETCHING_CHALLENGE_BYTES_FAILED;
+    UpdateStatus(/*step=*/Step::ERROR,
+                 /*payload=*/ErrorCode::FETCHING_CHALLENGE_BYTES_FAILED);
     QuickStartMetrics::RecordGaiaTransferAttempted(/*attempted=*/false);
-    NotifyObservers();
     return;
     // TODO(b:286853512) - Implement retry mechanism.
   }
@@ -380,15 +371,13 @@
 void TargetDeviceBootstrapController::OnFidoAssertionReceived(
     absl::optional<FidoAssertionInfo> assertion) {
   if (!assertion.has_value()) {
-    status_.step = Step::ERROR;
-    status_.payload = ErrorCode::GAIA_ASSERTION_NOT_RECEIVED;
-    NotifyObservers();
+    UpdateStatus(/*step=*/Step::ERROR,
+                 /*payload=*/ErrorCode::GAIA_ASSERTION_NOT_RECEIVED);
     return;
   }
 
-  status_.step = Step::TRANSFERRED_GOOGLE_ACCOUNT_DETAILS;
-  status_.payload.emplace<FidoAssertionInfo>(assertion.value());
-  NotifyObservers();
+  UpdateStatus(/*step=*/Step::TRANSFERRED_GOOGLE_ACCOUNT_DETAILS,
+               /*payload=*/assertion.value());
 }
 
 void TargetDeviceBootstrapController::CleanupIfNeeded() {
diff --git a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h
index 74d041e..3046f83 100644
--- a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h
+++ b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller.h
@@ -57,17 +57,20 @@
     FETCHING_CHALLENGE_BYTES_FAILED,
   };
 
-  using Payload = absl::
-      variant<absl::monostate, ErrorCode, QRCode::PixelData, FidoAssertionInfo>;
+  using Pin = std::string;
 
-  // TODO(b/288054370) - Consolidate fields.
+  using Payload = absl::variant<absl::monostate,
+                                ErrorCode,
+                                QRCode::PixelData,
+                                Pin,
+                                mojom::WifiCredentials,
+                                FidoAssertionInfo>;
+
   struct Status {
     Status();
     ~Status();
     Step step = Step::NONE;
     Payload payload;
-    mojom::WifiCredentials wifi_credentials;
-    std::string pin;
   };
 
   class AccessibilityManagerWrapper {
@@ -158,6 +161,7 @@
  private:
   friend class TargetDeviceBootstrapControllerTest;
 
+  void UpdateStatus(Step step, Payload payload);
   void NotifyObservers();
   void OnStartAdvertisingResult(bool success);
   void OnStopAdvertising();
@@ -190,7 +194,6 @@
 
   std::unique_ptr<TargetDeviceConnectionBroker> connection_broker_;
 
-  std::string pin_;
   // TODO: Should we enforce one observer at a time here too?
   base::ObserverList<Observer> observers_;
 
diff --git a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc
index effd3e9e..9cf4ece 100644
--- a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc
@@ -47,6 +47,7 @@
 using Observer = TargetDeviceBootstrapController::Observer;
 using Status = TargetDeviceBootstrapController::Status;
 using Step = TargetDeviceBootstrapController::Step;
+using Payload = TargetDeviceBootstrapController::Payload;
 using ErrorCode = TargetDeviceBootstrapController::ErrorCode;
 using ConnectionClosedReason =
     TargetDeviceConnectionBroker::ConnectionClosedReason;
@@ -66,9 +67,11 @@
     ASSERT_NE(status.step, last_status.step);
 
     last_status = status;
+    num_on_status_changed_called++;
   }
 
   Status last_status;
+  int num_on_status_changed_called = 0;
 };
 
 constexpr char kFakeChallengeBytesBase64[] =
@@ -162,6 +165,10 @@
     return bootstrap_controller_->session_context_;
   }
 
+  void UpdateStatus(Step step, Payload payload) {
+    bootstrap_controller_->UpdateStatus(step, payload);
+  }
+
  protected:
   absl::optional<FidoAssertionInfo> assertion_info_;
   base::test::SingleThreadTaskEnvironment task_environment_;
@@ -278,9 +285,11 @@
   fake_target_device_connection_broker_->InitiateConnection(kSourceDeviceId);
 
   EXPECT_EQ(fake_observer_->last_status.step, Step::PIN_VERIFICATION);
-  // TODO: Test PIN payload
-  EXPECT_TRUE(absl::holds_alternative<absl::monostate>(
+  EXPECT_TRUE(absl::holds_alternative<TargetDeviceBootstrapController::Pin>(
       fake_observer_->last_status.payload));
+  EXPECT_TRUE(absl::get<TargetDeviceBootstrapController::Pin>(
+                  fake_observer_->last_status.payload)
+                  .length() == 4);
 }
 
 TEST_F(TargetDeviceBootstrapControllerTest, AuthenticateConnection) {
@@ -435,7 +444,7 @@
                                  /*is_hidden=*/true, "password"));
 
   EXPECT_EQ(fake_observer_->last_status.step, Step::WIFI_CREDENTIALS_RECEIVED);
-  EXPECT_TRUE(absl::holds_alternative<absl::monostate>(
+  EXPECT_TRUE(absl::holds_alternative<mojom::WifiCredentials>(
       fake_observer_->last_status.payload));
   histogram_tester_.ExpectBucketCount(kWifiTransferResultHistogramName, true,
                                       1);
@@ -690,4 +699,17 @@
   EXPECT_EQ(expected_shared_secret, GetSessionContext().shared_secret());
 }
 
+TEST_F(TargetDeviceBootstrapControllerTest,
+       ObserversAreNotNotifiedIfStatusStepIsSame) {
+  EXPECT_EQ(0, fake_observer_->num_on_status_changed_called);
+  UpdateStatus(/*step=*/Step::REQUESTING_WIFI_CREDENTIALS,
+               /*payload=*/absl::monostate());
+  EXPECT_EQ(1, fake_observer_->num_on_status_changed_called);
+
+  // Updating status again with the same step shouldn't notify observers.
+  UpdateStatus(/*step=*/Step::REQUESTING_WIFI_CREDENTIALS,
+               /*payload=*/absl::monostate());
+  EXPECT_EQ(1, fake_observer_->num_on_status_changed_called);
+}
+
 }  // namespace ash::quick_start
diff --git a/chrome/browser/ash/login/quickstart_controller.cc b/chrome/browser/ash/login/quickstart_controller.cc
index 2408a52..57d69ee0 100644
--- a/chrome/browser/ash/login/quickstart_controller.cc
+++ b/chrome/browser/ash/login/quickstart_controller.cc
@@ -157,6 +157,7 @@
     const TargetDeviceBootstrapController::Status& status) {
   using Step = TargetDeviceBootstrapController::Step;
   using ErrorCode = TargetDeviceBootstrapController::ErrorCode;
+  using Pin = TargetDeviceBootstrapController::Pin;
 
   // TODO(b/298042953): Emit ScreenOpened metrics when automatically resuming
   // after an update.
@@ -174,8 +175,9 @@
       QS_LOG(INFO) << "Hit screen which is not implemented. Continuing";
       return;
     case Step::PIN_VERIFICATION:
-      CHECK(status.pin.length() == 4);
-      pin_ = status.pin;
+      CHECK(absl::holds_alternative<Pin>(status.payload));
+      pin_ = absl::get<Pin>(status.payload);
+      CHECK(pin_.value().length() == 4);
       UpdateUiState(UiState::SHOWING_PIN);
       QuickStartMetrics::RecordScreenOpened(
           QuickStartMetrics::ScreenName::kSetUpAndroidPhone);
@@ -190,9 +192,12 @@
           QuickStartMetrics::ScreenName::kConnectingToWifi);
       return;
     case Step::WIFI_CREDENTIALS_RECEIVED:
+      CHECK(absl::holds_alternative<mojom::WifiCredentials>(status.payload));
+
       LoginDisplayHost::default_host()
           ->GetWizardContext()
-          ->quick_start_wifi_credentials = status.wifi_credentials;
+          ->quick_start_wifi_credentials =
+          absl::get<mojom::WifiCredentials>(status.payload);
       ABSL_FALLTHROUGH_INTENDED;
     case Step::EMPTY_WIFI_CREDENTIALS_RECEIVED:
       UpdateUiState(UiState::WIFI_CREDENTIALS_RECEIVED);
diff --git a/chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.cc b/chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.cc
new file mode 100644
index 0000000..1c4bc2bb
--- /dev/null
+++ b/chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.cc
@@ -0,0 +1,116 @@
+// 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/factor_setup_success_screen.h"
+
+#include <string>
+#include <utility>
+
+#include "ash/constants/ash_features.h"
+#include "base/check_op.h"
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/ash/login/screens/base_screen.h"
+#include "chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.h"
+#include "chrome/browser/ash/login/wizard_context.h"
+#include "chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.h"
+#include "chromeos/ash/components/osauth/public/auth_session_storage.h"
+#include "chromeos/ash/components/osauth/public/common_types.h"
+
+namespace ash {
+namespace {
+
+// LINT.IfChange
+constexpr const char kUserActionProceed[] = "proceed";
+
+std::string GetChangeModeString(WizardContext::AuthChangeFlow flow) {
+  switch (flow) {
+    case WizardContext::AuthChangeFlow::kRecovery:
+      return "update";
+    case WizardContext::AuthChangeFlow::kInitialSetup:
+      return "set";
+  }
+}
+
+std::string GetModifiedFactorsString(AuthFactorsSet factors) {
+  if (factors == AuthFactorsSet({AshAuthFactor::kGaiaPassword})) {
+    return "online";
+  } else if (factors == AuthFactorsSet({AshAuthFactor::kLocalPassword})) {
+    return "local";
+  } else if (factors == AuthFactorsSet({AshAuthFactor::kCryptohomePin})) {
+    return "pin";
+  } else if (factors == AuthFactorsSet({AshAuthFactor::kGaiaPassword,
+                                        AshAuthFactor::kCryptohomePin})) {
+    return "online+pin";
+  } else if (factors == AuthFactorsSet({AshAuthFactor::kLocalPassword,
+                                        AshAuthFactor::kCryptohomePin})) {
+    return "local+pin";
+  }
+  // Fallback for sanity:
+  return "online";
+}
+// LINT.ThenChange(/chrome/browser/resources/chromeos/login/screens/osauth/factor_setup_success.js)
+
+}  // namespace
+
+// static
+std::string FactorSetupSuccessScreen::GetResultString(Result result) {
+  switch (result) {
+    case Result::kNotApplicable:
+      return BaseScreen::kNotApplicable;
+    case Result::kProceed:
+      return "Proceed";
+  }
+}
+
+FactorSetupSuccessScreen::FactorSetupSuccessScreen(
+    base::WeakPtr<FactorSetupSuccessScreenView> view,
+    ScreenExitCallback exit_callback)
+    : BaseScreen(FactorSetupSuccessScreenView::kScreenId,
+                 OobeScreenPriority::DEFAULT),
+      view_(std::move(view)),
+      exit_callback_(std::move(exit_callback)) {}
+
+FactorSetupSuccessScreen::~FactorSetupSuccessScreen() = default;
+
+bool FactorSetupSuccessScreen::MaybeSkip(WizardContext& wizard_context) {
+  if (wizard_context.skip_post_login_screens_for_tests) {
+    exit_callback_.Run(Result::kNotApplicable);
+    return true;
+  }
+  if (wizard_context.knowledge_factor_setup.modified_factors.Empty()) {
+    exit_callback_.Run(Result::kNotApplicable);
+    return true;
+  }
+  return false;
+}
+
+void FactorSetupSuccessScreen::HideImpl() {}
+
+void FactorSetupSuccessScreen::ShowImpl() {
+  if (!view_) {
+    return;
+  }
+  base::Value::Dict params;
+  params.Set("modifiedFactors",
+             GetModifiedFactorsString(
+                 context()->knowledge_factor_setup.modified_factors));
+  params.Set(
+      "changeMode",
+      GetChangeModeString(context()->knowledge_factor_setup.auth_setup_flow));
+  view_->Show(std::move(params));
+}
+
+void FactorSetupSuccessScreen::OnUserAction(const base::Value::List& args) {
+  CHECK_GE(args.size(), 1u);
+  const std::string& action_id = args[0].GetString();
+  if (action_id == kUserActionProceed) {
+    exit_callback_.Run(Result::kProceed);
+    return;
+  }
+  BaseScreen::OnUserAction(args);
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.h b/chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.h
new file mode 100644
index 0000000..a12f1c9
--- /dev/null
+++ b/chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.h
@@ -0,0 +1,54 @@
+// 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_ASH_LOGIN_SCREENS_OSAUTH_FACTOR_SETUP_SUCCESS_SCREEN_H_
+#define CHROME_BROWSER_ASH_LOGIN_SCREENS_OSAUTH_FACTOR_SETUP_SUCCESS_SCREEN_H_
+
+#include <string>
+
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/ash/login/screens/osauth/base_osauth_setup_screen.h"
+
+namespace ash {
+
+class FactorSetupSuccessScreenView;
+class WizardContext;
+
+// Screen to inform the user that their authentication factors were
+// successfully updated during prior operations.
+class FactorSetupSuccessScreen : public BaseScreen {
+ public:
+  using TView = FactorSetupSuccessScreenView;
+  enum class Result {
+    kNotApplicable,
+    kProceed,
+  };
+
+  static std::string GetResultString(Result result);
+  using ScreenExitCallback = base::RepeatingCallback<void(Result)>;
+
+  FactorSetupSuccessScreen(base::WeakPtr<FactorSetupSuccessScreenView> view,
+                           ScreenExitCallback exit_callback);
+  ~FactorSetupSuccessScreen() override;
+
+  FactorSetupSuccessScreen(const FactorSetupSuccessScreen&) = delete;
+  FactorSetupSuccessScreen& operator=(const FactorSetupSuccessScreen&) = delete;
+
+ private:
+  // BaseScreen:
+  bool MaybeSkip(WizardContext& context) override;
+  void ShowImpl() override;
+  void HideImpl() override;
+  void OnUserAction(const base::Value::List& args) override;
+
+  base::WeakPtr<FactorSetupSuccessScreenView> view_ = nullptr;
+  ScreenExitCallback exit_callback_;
+  base::WeakPtrFactory<FactorSetupSuccessScreen> weak_ptr_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_SCREENS_OSAUTH_FACTOR_SETUP_SUCCESS_SCREEN_H_
diff --git a/chrome/browser/ash/login/screens/osauth/local_password_setup_screen.cc b/chrome/browser/ash/login/screens/osauth/local_password_setup_screen.cc
index 9824ea99..8cdba7a 100644
--- a/chrome/browser/ash/login/screens/osauth/local_password_setup_screen.cc
+++ b/chrome/browser/ash/login/screens/osauth/local_password_setup_screen.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/ash/login/wizard_context.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/ui/webui/ash/login/local_password_setup_handler.h"
+#include "chromeos/ash/components/osauth/public/common_types.h"
 #include "chromeos/ash/services/auth_factor_config/auth_factor_config.h"
 #include "chromeos/ash/services/auth_factor_config/in_process_instances.h"
 #include "chromeos/ash/services/auth_factor_config/public/mojom/auth_factor_config.mojom-forward.h"
@@ -24,7 +25,6 @@
 
 constexpr const char kUserActionInputPassword[] = "inputPassword";
 constexpr const char kUserActionBack[] = "back";
-constexpr const char kUserActionDone[] = "done";
 
 }  // namespace
 
@@ -95,9 +95,6 @@
   } else if (action_id == kUserActionBack) {
     exit_callback_.Run(Result::kBack);
     return;
-  } else if (action_id == kUserActionDone) {
-    exit_callback_.Run(Result::kDone);
-    return;
   }
   BaseOSAuthSetupScreen::OnUserAction(args);
 }
@@ -112,7 +109,9 @@
     crash_reporter::DumpWithoutCrashing();
     return;
   }
-  view_->ShowLocalPasswordSetupSuccess();
+  context()->knowledge_factor_setup.modified_factors.Put(
+      AshAuthFactor::kLocalPassword);
+  exit_callback_.Run(Result::kDone);
 }
 
 void LocalPasswordSetupScreen::OnSetLocalPassword(
@@ -125,7 +124,9 @@
     crash_reporter::DumpWithoutCrashing();
     return;
   }
-  view_->ShowLocalPasswordSetupSuccess();
+  context()->knowledge_factor_setup.modified_factors.Put(
+      AshAuthFactor::kLocalPassword);
+  exit_callback_.Run(Result::kDone);
 }
 
 }  // namespace ash
diff --git a/chrome/browser/ash/login/wizard_context.h b/chrome/browser/ash/login/wizard_context.h
index 8e721a1..2e3a011 100644
--- a/chrome/browser/ash/login/wizard_context.h
+++ b/chrome/browser/ash/login/wizard_context.h
@@ -85,6 +85,8 @@
     bool local_password_forced = false;
 
     AuthChangeFlow auth_setup_flow = AuthChangeFlow::kInitialSetup;
+
+    AuthFactorsSet modified_factors;
   };
 
   // Configuration for automating OOBE screen actions, e.g. during device
diff --git a/chrome/browser/ash/login/wizard_controller.cc b/chrome/browser/ash/login/wizard_controller.cc
index c8f6858..a4dad3b 100644
--- a/chrome/browser/ash/login/wizard_controller.cc
+++ b/chrome/browser/ash/login/wizard_controller.cc
@@ -94,6 +94,7 @@
 #include "chrome/browser/ash/login/screens/osauth/apply_online_password_screen.h"
 #include "chrome/browser/ash/login/screens/osauth/cryptohome_recovery_screen.h"
 #include "chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen.h"
+#include "chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.h"
 #include "chrome/browser/ash/login/screens/osauth/gaia_password_changed_screen.h"
 #include "chrome/browser/ash/login/screens/osauth/gaia_password_changed_screen_legacy.h"
 #include "chrome/browser/ash/login/screens/osauth/local_password_setup_screen.h"
@@ -188,6 +189,7 @@
 #include "chrome/browser/ui/webui/ash/login/os_install_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/os_trial_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/osauth/apply_online_password_screen_handler.h"
+#include "chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/osauth/osauth_error_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/packaged_license_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/parental_handoff_screen_handler.h"
@@ -940,6 +942,10 @@
       oobe_ui->GetView<OSAuthErrorScreenHandler>()->AsWeakPtr(),
       base::BindRepeating(&WizardController::OnOSAuthErrorScreenExit,
                           weak_factory_.GetWeakPtr())));
+  append(std::make_unique<FactorSetupSuccessScreen>(
+      oobe_ui->GetView<FactorSetupSuccessScreenHandler>()->AsWeakPtr(),
+      base::BindRepeating(&WizardController::OnFactorSetupSuccessScreenExit,
+                          weak_factory_.GetWeakPtr())));
 
   return result;
 }
@@ -1124,6 +1130,10 @@
   SetCurrentScreen(GetScreen(OSAuthErrorScreenView::kScreenId));
 }
 
+void WizardController::ShowFactorSetupSuccessScreen() {
+  SetCurrentScreen(GetScreen(FactorSetupSuccessScreenView::kScreenId));
+}
+
 void WizardController::ShowFingerprintSetupScreen() {
   SetCurrentScreen(GetScreen(FingerprintSetupScreenView::kScreenId));
 }
@@ -2238,7 +2248,7 @@
       return;
     case LocalPasswordSetupScreen::Result::kDone:
     case LocalPasswordSetupScreen::Result::kNotApplicable:
-      ShowFingerprintSetupScreen();
+      ShowFactorSetupSuccessScreen();
       return;
   }
 }
@@ -2269,6 +2279,17 @@
   }
 }
 
+void WizardController::OnFactorSetupSuccessScreenExit(
+    FactorSetupSuccessScreen::Result result) {
+  OnScreenExit(FactorSetupSuccessScreenView::kScreenId,
+               FactorSetupSuccessScreen::GetResultString(result));
+  switch (result) {
+    case FactorSetupSuccessScreen::Result::kNotApplicable:
+    case FactorSetupSuccessScreen::Result::kProceed:
+      ShowFingerprintSetupScreen();
+  }
+}
+
 void WizardController::OnFingerprintSetupScreenExit(
     FingerprintSetupScreen::Result result) {
   OnScreenExit(FingerprintSetupScreenView::kScreenId,
@@ -2814,6 +2835,8 @@
     ShowApplyOnlinePasswordScreen();
   } else if (screen_id == OSAuthErrorScreenView::kScreenId) {
     ShowOSAuthErrorScreen();
+  } else if (screen_id == FactorSetupSuccessScreenView::kScreenId) {
+    ShowFactorSetupSuccessScreen();
   } else if (screen_id == LocalPasswordSetupView::kScreenId) {
     ShowLocalPasswordSetupScreen();
   } else if (screen_id == TpmErrorView::kScreenId ||
diff --git a/chrome/browser/ash/login/wizard_controller.h b/chrome/browser/ash/login/wizard_controller.h
index 4ed6145..04bcf933 100644
--- a/chrome/browser/ash/login/wizard_controller.h
+++ b/chrome/browser/ash/login/wizard_controller.h
@@ -57,6 +57,7 @@
 #include "chrome/browser/ash/login/screens/osauth/apply_online_password_screen.h"
 #include "chrome/browser/ash/login/screens/osauth/cryptohome_recovery_screen.h"
 #include "chrome/browser/ash/login/screens/osauth/cryptohome_recovery_setup_screen.h"
+#include "chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.h"
 #include "chrome/browser/ash/login/screens/osauth/gaia_password_changed_screen.h"
 #include "chrome/browser/ash/login/screens/osauth/gaia_password_changed_screen_legacy.h"
 #include "chrome/browser/ash/login/screens/osauth/local_password_setup_screen.h"
@@ -349,6 +350,7 @@
   void ShowLocalPasswordSetupScreen();
   void ShowApplyOnlinePasswordScreen();
   void ShowOSAuthErrorScreen();
+  void ShowFactorSetupSuccessScreen();
 
   // Shows images login screen.
   void ShowLoginScreen();
@@ -452,6 +454,7 @@
   void OnApplyOnlinePasswordScreenExit(
       ApplyOnlinePasswordScreen::Result result);
   void OnOSAuthErrorScreenExit(OSAuthErrorScreen::Result result);
+  void OnFactorSetupSuccessScreenExit(FactorSetupSuccessScreen::Result result);
   // Callback invoked once it has been determined whether the device is disabled
   // or not.
   void OnDeviceDisabledChecked(bool device_disabled);
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
index 08bdc11..784638a8 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
@@ -44,6 +44,7 @@
 #include "chrome/common/pref_names.h"
 #include "chromeos/ash/components/install_attributes/install_attributes.h"
 #include "chromeos/ash/components/system/statistics_provider.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "components/policy/core/common/cloud/cloud_external_data_manager.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/policy/core/common/cloud/cloud_policy_core.h"
@@ -264,9 +265,15 @@
     CreateStatusUploader(managed_session_service_.get());
     syslog_uploader_ =
         std::make_unique<SystemLogUploader>(nullptr, task_runner_);
-    heartbeat_scheduler_ = std::make_unique<HeartbeatScheduler>(
-        g_browser_process->gcm_driver(), client(), device_store_.get(),
-        install_attributes->GetDeviceId(), task_runner_);
+    if (base::FeatureList::IsEnabled(
+            chromeos::features::kKioskHeartbeatsViaERP)) {
+      // Do nothing as heartbeats go over ERP.
+    } else {
+      // Initialize legacy GCM heartbeat (default behaviour)
+      heartbeat_scheduler_ = std::make_unique<HeartbeatScheduler>(
+          g_browser_process->gcm_driver(), client(), device_store_.get(),
+          install_attributes->GetDeviceId(), task_runner_);
+    }
     metric_reporting_manager_ = reporting::MetricReportingManager::Create(
         managed_session_service_.get());
     os_updates_reporter_ = reporting::OsUpdatesReporter::Create();
@@ -407,4 +414,8 @@
       managed_session_service_.get());
 }
 
+HeartbeatScheduler*
+DeviceCloudPolicyManagerAsh::GetHeartbeatSchedulerForTesting() const {
+  return heartbeat_scheduler_.get();
+}
 }  // namespace policy
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h
index f5919fc..63378374 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h
@@ -167,6 +167,8 @@
   void OnUserRemoved(const AccountId& account_id,
                      user_manager::UserRemovalReason reason) override;
 
+  HeartbeatScheduler* GetHeartbeatSchedulerForTesting() const;
+
  protected:
   // Object that monitors managed session related events used by reporting
   // services, protected for testing.
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc
index 2982d82..74c0c7ce 100644
--- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc
+++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash_unittest.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/ash/policy/enrollment/enrollment_requisition_manager.h"
 #include "chrome/browser/ash/policy/enrollment/enrollment_status.h"
 #include "chrome/browser/ash/policy/remote_commands/fake_start_crd_session_job_delegate.h"
+#include "chrome/browser/ash/policy/uploading/heartbeat_scheduler.h"
 #include "chrome/browser/ash/settings/device_settings_test_helper.h"
 #include "chrome/browser/device_identity/device_oauth2_token_service.h"
 #include "chrome/browser/device_identity/device_oauth2_token_service_factory.h"
@@ -49,6 +50,7 @@
 #include "chromeos/ash/components/install_attributes/install_attributes.h"
 #include "chromeos/ash/components/system/fake_statistics_provider.h"
 #include "chromeos/ash/components/system/statistics_provider.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/core/common/cloud/cloud_policy_core.h"
@@ -928,6 +930,24 @@
   ExpectSuccessfulEnrollment();
 }
 
+TEST_P(DeviceCloudPolicyManagerAshEnrollmentTest,
+       EnabledKioskHeartbeatsViaERP) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(chromeos::features::kKioskHeartbeatsViaERP);
+
+  RunTest();
+  ExpectSuccessfulEnrollment();
+  EXPECT_FALSE(manager_->GetHeartbeatSchedulerForTesting());
+}
+
+TEST_P(DeviceCloudPolicyManagerAshEnrollmentTest,
+       DisabledKioskHeartbeatsViaERP) {
+  RunTest();
+  ExpectSuccessfulEnrollment();
+  EXPECT_EQ(manager_->GetHeartbeatSchedulerForTesting()->last_heartbeat(),
+            base::Time());
+}
+
 TEST_P(DeviceCloudPolicyManagerAshEnrollmentTest, Reenrollment) {
   LockDevice();
   // Normally this happens at signin screen profile creation. But
diff --git a/chrome/browser/ash/policy/dlp/dlp_files_controller_ash.cc b/chrome/browser/ash/policy/dlp/dlp_files_controller_ash.cc
index 6b84aec..59df37f 100644
--- a/chrome/browser/ash/policy/dlp/dlp_files_controller_ash.cc
+++ b/chrome/browser/ash/policy/dlp/dlp_files_controller_ash.cc
@@ -871,11 +871,6 @@
   data_controls::DlpHistogramEnumeration(
       data_controls::dlp::kFilesUnknownAccessLevel, level);
 
-  MaybeReportEvent(
-      file.inode, file.crtime, file.path, src_pattern,
-      DlpFileDestination(data_controls::Component::kUnknownComponent),
-      absl::nullopt, rule_metadata, level);
-
   return restricted;
 }
 
diff --git a/chrome/browser/ash/policy/dlp/dlp_files_controller_ash_unittest.cc b/chrome/browser/ash/policy/dlp/dlp_files_controller_ash_unittest.cc
index 9bdc7e1..3b1b6bd 100644
--- a/chrome/browser/ash/policy/dlp/dlp_files_controller_ash_unittest.cc
+++ b/chrome/browser/ash/policy/dlp/dlp_files_controller_ash_unittest.cc
@@ -1124,31 +1124,7 @@
       .WillOnce(
           testing::DoAll(testing::SetArgPointee<2>(kExampleSourcePattern4),
                          testing::SetArgPointee<3>(kRuleMetadata4),
-                         testing::Return(DlpRulesManager::Level::kAllow)))
-      .WillOnce(
-          testing::DoAll(testing::SetArgPointee<2>(kExampleSourcePattern1),
-                         testing::SetArgPointee<3>(kRuleMetadata1),
-                         testing::Return(DlpRulesManager::Level::kBlock)))
-      .WillOnce(
-          testing::DoAll(testing::SetArgPointee<2>(kExampleSourcePattern2),
-                         testing::SetArgPointee<3>(kRuleMetadata2),
-                         testing::Return(DlpRulesManager::Level::kReport)))
-      .WillOnce(
-          testing::DoAll(testing::SetArgPointee<2>(kExampleSourcePattern3),
-                         testing::SetArgPointee<3>(kRuleMetadata3),
-                         testing::Return(DlpRulesManager::Level::kWarn)))
-      .WillOnce(
-          testing::DoAll(testing::SetArgPointee<2>(kExampleSourcePattern1),
-                         testing::SetArgPointee<3>(kRuleMetadata1),
-                         testing::Return(DlpRulesManager::Level::kBlock)))
-      .WillOnce(
-          testing::DoAll(testing::SetArgPointee<2>(kExampleSourcePattern2),
-                         testing::SetArgPointee<3>(kRuleMetadata2),
-                         testing::Return(DlpRulesManager::Level::kReport)))
-      .WillOnce(
-          testing::DoAll(testing::SetArgPointee<2>(kExampleSourcePattern1),
-                         testing::SetArgPointee<3>(kRuleMetadata1),
-                         testing::Return(DlpRulesManager::Level::kWarn)));
+                         testing::Return(DlpRulesManager::Level::kAllow)));
 
   EXPECT_CALL(*rules_manager_, GetReportingManager).Times(testing::AnyNumber());
 
@@ -1167,69 +1143,12 @@
       kInode4, kCrtime4, base::FilePath(kFilePath4), kExampleUrl4,
       kReferrerUrl4);
 
-  auto CreateEvent =
-      [](const std::string& src_pattern, DlpRulesManager::Level level,
-         const std::string& filename, const std::string& rule_name,
-         const std::string& rule_id) {
-        auto event_builder = data_controls::DlpPolicyEventBuilder::Event(
-            src_pattern, rule_name, rule_id,
-            DlpRulesManager::Restriction::kFiles, level);
-        event_builder->SetDestinationComponent(
-            data_controls::Component::kUnknownComponent);
-        event_builder->SetContentName(filename);
-        return event_builder->Create();
-      };
-
-  const auto event1 =
-      CreateEvent(kExampleSourcePattern1, DlpRulesManager::Level::kBlock,
-                  kFilePath1, kRuleName1, kRuleId1);
-  const auto event2 =
-      CreateEvent(kExampleSourcePattern2, DlpRulesManager::Level::kReport,
-                  kFilePath2, kRuleName2, kRuleId2);
-  const auto event3 =
-      CreateEvent(kExampleSourcePattern3, DlpRulesManager::Level::kWarn,
-                  kFilePath3, kRuleName3, kRuleId3);
-
-  base::TimeDelta cooldown_time =
-      event_storage_->GetDeduplicationCooldownForTesting();
-
-  // Report `event1`, `event2`, and `event3` after these calls.
   ASSERT_TRUE(files_controller_->IsDlpPolicyMatched(file1));
   ASSERT_FALSE(files_controller_->IsDlpPolicyMatched(file2));
   ASSERT_TRUE(files_controller_->IsDlpPolicyMatched(file3));
   ASSERT_FALSE(files_controller_->IsDlpPolicyMatched(file4));
 
-  task_runner_->FastForwardBy(cooldown_time);
-
-  // Report `event1`, `event2`, and `event3` after these calls.
-  ASSERT_TRUE(files_controller_->IsDlpPolicyMatched(file1));
-  ASSERT_FALSE(files_controller_->IsDlpPolicyMatched(file2));
-  ASSERT_TRUE(files_controller_->IsDlpPolicyMatched(file3));
-
-  task_runner_->FastForwardBy(cooldown_time / 2);
-
-  // Do not report after these calls.
-  ASSERT_TRUE(files_controller_->IsDlpPolicyMatched(file1));
-  ASSERT_FALSE(files_controller_->IsDlpPolicyMatched(file2));
-  ASSERT_TRUE(files_controller_->IsDlpPolicyMatched(file3));
-
-  const auto expected_events = std::vector<const DlpPolicyEvent*>(
-      {&event1, &event2, &event3, &event1, &event2, &event3});
-
-  ASSERT_EQ(events.size(), 6u);
-  for (size_t i = 0; i < events.size(); ++i) {
-    EXPECT_THAT(events[i],
-                data_controls::IsDlpPolicyEvent(*expected_events[i]));
-  }
-
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  data_controls::GetDlpHistogramPrefix() +
-                  std::string(data_controls::dlp::kFilesUnknownAccessLevel)),
-              base::BucketsAre(
-                  base::Bucket(policy::DlpRulesManager::Level::kBlock, 3),
-                  base::Bucket(policy::DlpRulesManager::Level::kReport, 3),
-                  base::Bucket(policy::DlpRulesManager::Level::kWarn, 3),
-                  base::Bucket(policy::DlpRulesManager::Level::kAllow, 1)));
+  ASSERT_EQ(events_.size(), 0u);
 }
 
 TEST_F(DlpFilesControllerAshTest, CheckReportingOnIsFilesTransferRestricted) {
@@ -1350,9 +1269,9 @@
   const auto expected_events =
       std::vector<const DlpPolicyEvent*>({&event1, &event2, &event1, &event2});
 
-  ASSERT_EQ(events.size(), 4u);
-  for (size_t i = 0; i < events.size(); ++i) {
-    EXPECT_THAT(events[i],
+  ASSERT_EQ(events_.size(), 4u);
+  for (size_t i = 0; i < events_.size(); ++i) {
+    EXPECT_THAT(events_[i],
                 data_controls::IsDlpPolicyEvent(*expected_events[i]));
   }
 }
@@ -1412,8 +1331,8 @@
   // Do not report after these calls
   ASSERT_TRUE(files_controller_->IsDlpPolicyMatched(file1));
 
-  ASSERT_EQ(events.size(), 1u);
-  EXPECT_THAT(events[0], data_controls::IsDlpPolicyEvent(event));
+  ASSERT_EQ(events_.size(), 1u);
+  EXPECT_THAT(events_[0], data_controls::IsDlpPolicyEvent(event));
 }
 
 // Test that no file event is generated for a selected list of system apps.
@@ -1595,7 +1514,7 @@
       /*task_id=*/1234, transferred_files, DlpFileDestination(),
       dlp::FileAction::kTransfer, cb.Get());
 
-  ASSERT_EQ(events.size(), 0u);
+  ASSERT_EQ(events_.size(), 0u);
 }
 
 class DlpFilesExternalDestinationTest
@@ -1684,15 +1603,15 @@
       DlpFileDestination(expected_component), dlp::FileAction::kTransfer,
       cb.Get());
 
-  ASSERT_EQ(events.size(), 2u);
+  ASSERT_EQ(events_.size(), 2u);
   EXPECT_THAT(
-      events[0],
+      events_[0],
       data_controls::IsDlpPolicyEvent(data_controls::CreateDlpPolicyEvent(
           kExampleSourcePattern1, expected_component,
           DlpRulesManager::Restriction::kFiles, kRuleName1, kRuleId1,
           DlpRulesManager::Level::kBlock)));
   EXPECT_THAT(
-      events[1],
+      events_[1],
       data_controls::IsDlpPolicyEvent(data_controls::CreateDlpPolicyEvent(
           kExampleSourcePattern3, expected_component,
           DlpRulesManager::Restriction::kFiles, kRuleName3, kRuleId3,
@@ -1727,7 +1646,7 @@
   files_controller_->CheckIfDownloadAllowed(
       DlpFileDestination(GURL(kExampleUrl1)), dst_url.path(), cb.Get());
 
-  ASSERT_EQ(events.size(), 1u);
+  ASSERT_EQ(events_.size(), 1u);
 
   auto event_builder = data_controls::DlpPolicyEventBuilder::Event(
       kExampleSourcePattern1, kRuleName1, kRuleId1,
@@ -1736,7 +1655,7 @@
   event_builder->SetDestinationComponent(expected_component);
   event_builder->SetContentName(base::FilePath(path).BaseName().value());
 
-  EXPECT_THAT(events[0],
+  EXPECT_THAT(events_[0],
               data_controls::IsDlpPolicyEvent(event_builder->Create()));
 }
 
@@ -1856,10 +1775,10 @@
       DlpFileDestination(GURL(destination_url)), dlp::FileAction::kDownload,
       cb.Get());
 
-  ASSERT_EQ(events.size(), disallowed_source_patterns.size());
+  ASSERT_EQ(events_.size(), disallowed_source_patterns.size());
   for (size_t i = 0u; i < disallowed_source_patterns.size(); ++i) {
     EXPECT_THAT(
-        events[i],
+        events_[i],
         data_controls::IsDlpPolicyEvent(data_controls::CreateDlpPolicyEvent(
             disallowed_source_patterns[i], destination_pattern,
             DlpRulesManager::Restriction::kFiles, triggered_rule_names[i],
@@ -1945,11 +1864,11 @@
     return event_builder->Create();
   };
 
-  ASSERT_EQ(events.size(), 1u + (choice_result ? 1 : 0));
-  EXPECT_THAT(events[0], data_controls::IsDlpPolicyEvent(
-                             CreateEvent(DlpRulesManager::Level::kWarn)));
+  ASSERT_EQ(events_.size(), 1u + (choice_result ? 1 : 0));
+  EXPECT_THAT(events_[0], data_controls::IsDlpPolicyEvent(
+                              CreateEvent(DlpRulesManager::Level::kWarn)));
   if (choice_result) {
-    EXPECT_THAT(events[1],
+    EXPECT_THAT(events_[1],
                 data_controls::IsDlpPolicyEvent(CreateEvent(absl::nullopt)));
   }
 
diff --git a/chrome/browser/ash/policy/dlp/test/dlp_files_test_with_mounts.cc b/chrome/browser/ash/policy/dlp/test/dlp_files_test_with_mounts.cc
index 2ef37258..436298c 100644
--- a/chrome/browser/ash/policy/dlp/test/dlp_files_test_with_mounts.cc
+++ b/chrome/browser/ash/policy/dlp/test/dlp_files_test_with_mounts.cc
@@ -93,7 +93,7 @@
 
   reporting_manager_ = std::make_unique<data_controls::DlpReportingManager>();
   SetReportQueueForReportingManager(
-      reporting_manager_.get(), events,
+      reporting_manager_.get(), events_,
       base::SequencedTaskRunner::GetCurrentDefault());
   ON_CALL(*rules_manager_, GetReportingManager)
       .WillByDefault(::testing::Return(reporting_manager_.get()));
diff --git a/chrome/browser/ash/policy/dlp/test/dlp_files_test_with_mounts.h b/chrome/browser/ash/policy/dlp/test/dlp_files_test_with_mounts.h
index 11c7205..05a285f 100644
--- a/chrome/browser/ash/policy/dlp/test/dlp_files_test_with_mounts.h
+++ b/chrome/browser/ash/policy/dlp/test/dlp_files_test_with_mounts.h
@@ -44,7 +44,7 @@
       fpnm_ = nullptr;
   std::unique_ptr<DlpFilesControllerAsh> files_controller_;
   std::unique_ptr<data_controls::DlpReportingManager> reporting_manager_;
-  std::vector<DlpPolicyEvent> events;
+  std::vector<DlpPolicyEvent> events_;
   raw_ptr<DlpFilesEventStorage, ExperimentalAsh> event_storage_ = nullptr;
 
   scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
diff --git a/chrome/browser/attribution_reporting/chrome_attribution_browsertest.cc b/chrome/browser/attribution_reporting/chrome_attribution_browsertest.cc
index a01db779..35ce715 100644
--- a/chrome/browser/attribution_reporting/chrome_attribution_browsertest.cc
+++ b/chrome/browser/attribution_reporting/chrome_attribution_browsertest.cc
@@ -172,9 +172,14 @@
   PrivacySandboxSettingsFactory::GetForProfile(browser()->profile())
       ->SetAllPrivacySandboxAllowedForTesting();
 
-  // We do not add an attestation for the reporting origin.
+  // We add an empty attestation for the reporting origin. If feature
+  // `kDefaultAllowPrivacySandboxAttestations` is enabled, the attestation is
+  // default allowed if the map is absent. Creating a map with the reporting
+  // endpoint mapping to an empty set will make sure the attestation will fail.
   privacy_sandbox::PrivacySandboxAttestations::GetInstance()
-      ->SetAttestationsForTesting({});
+      ->SetAttestationsForTesting(
+          privacy_sandbox::PrivacySandboxAttestationsMap{
+              {net::SchemefulSite(GURL(kReportEndpoint)), {}}});
 
   ExpectedReportWaiter expected_report(GURL(kReportEndpoint), &server_);
   ASSERT_TRUE(server_.Start());
diff --git a/chrome/browser/autofill/autofill_interactive_uitest.cc b/chrome/browser/autofill/autofill_interactive_uitest.cc
index df1cfd3e..9b60be0 100644
--- a/chrome/browser/autofill/autofill_interactive_uitest.cc
+++ b/chrome/browser/autofill/autofill_interactive_uitest.cc
@@ -2735,7 +2735,6 @@
     std::vector<base::test::FeatureRef> disabled;
     if (GetParam() != FrameType::kIFrame) {
       enabled.push_back({blink::features::kBrowsingTopics, {}});
-      enabled.push_back({blink::features::kBrowsingTopicsXHR, {}});
       enabled.push_back({blink::features::kFencedFramesAPIChanges, {}});
       scoped_feature_list_.InitWithFeaturesAndParameters(enabled, disabled);
       fenced_frame_test_helper_ =
diff --git a/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc b/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
index 21ca099..f40257bb 100644
--- a/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
+++ b/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
@@ -355,7 +355,6 @@
 PrivacySandboxSettings3: disabled
 OverridePrivacySandboxSettingsLocalTesting: disabled
 BrowsingTopicsBypassIPIsPubliclyRoutableCheck: disabled
-BrowsingTopicsXHR: disabled
 BrowsingTopicsDocumentAPI: enabled
 Configuration version: 1
 BrowsingTopicsParameters: disabled
@@ -471,7 +470,6 @@
 PrivacySandboxSettings3: enabled
 OverridePrivacySandboxSettingsLocalTesting: disabled
 BrowsingTopicsBypassIPIsPubliclyRoutableCheck: disabled
-BrowsingTopicsXHR: disabled
 BrowsingTopicsDocumentAPI: enabled
 Configuration version: 1
 BrowsingTopicsParameters: enabled
diff --git a/chrome/browser/browsing_topics/browsing_topics_service_browsertest.cc b/chrome/browser/browsing_topics/browsing_topics_service_browsertest.cc
index 1faa36b..651d484 100644
--- a/chrome/browser/browsing_topics/browsing_topics_service_browsertest.cc
+++ b/chrome/browser/browsing_topics/browsing_topics_service_browsertest.cc
@@ -306,7 +306,7 @@
   BrowsingTopicsAnnotationGoldenDataBrowserTest() {
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/
-        {blink::features::kBrowsingTopics, blink::features::kBrowsingTopicsXHR,
+        {blink::features::kBrowsingTopics,
          blink::features::kBrowsingTopicsBypassIPIsPubliclyRoutableCheck,
          features::kPrivacySandboxAdsAPIsOverride, blink::features::kPortals},
         /*disabled_features=*/{
@@ -396,7 +396,7 @@
                                 base::Unretained(this))) {
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/
-        {blink::features::kBrowsingTopics, blink::features::kBrowsingTopicsXHR,
+        {blink::features::kBrowsingTopics,
          blink::features::kBrowsingTopicsBypassIPIsPubliclyRoutableCheck,
          features::kPrivacySandboxAdsAPIsOverride, blink::features::kPortals},
         /*disabled_features=*/{});
@@ -1720,172 +1720,6 @@
   EXPECT_EQ(api_usage_contexts[1].hashed_context_domain, HashedDomain(1));
 }
 
-IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, XhrWithoutTopicsFlagSet) {
-  GURL main_frame_url =
-      https_server_.GetURL("b.test", "/browsing_topics/empty_page.html");
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
-
-  GURL xhr_url = https_server_.GetURL(
-      "b.test", "/browsing_topics/page_with_custom_topics_header.html");
-
-  {
-    // Send a XHR without the `deprecatedBrowsingTopics` flag. This request
-    // isn't eligible for topics.
-    EXPECT_EQ("success", EvalJs(web_contents()->GetPrimaryMainFrame(),
-                                content::JsReplace(R"(
-      const xhr = new XMLHttpRequest();
-
-      new Promise(resolve => {
-        xhr.onreadystatechange = function() {
-          if (xhr.readyState == XMLHttpRequest.DONE) {
-            resolve('success');
-          }
-        }
-
-        xhr.open('GET', $1);
-        xhr.send();
-      });)",
-                                                   xhr_url)));
-
-    absl::optional<std::string> topics_header_value =
-        GetTopicsHeaderForRequestPath(
-            "/browsing_topics/page_with_custom_topics_header.html");
-
-    // Expect no topics header as the request did not set
-    // xhr.deprecatedBrowsingTopics.
-    EXPECT_FALSE(topics_header_value);
-  }
-
-  {
-    // Send a XHR with the `deprecatedBrowsingTopics` flag set to false. This
-    // request isn't eligible for topics.
-    EXPECT_EQ("success", EvalJs(web_contents()->GetPrimaryMainFrame(),
-                                content::JsReplace(R"(
-      const xhr = new XMLHttpRequest();
-
-      new Promise(resolve => {
-        xhr.onreadystatechange = function() {
-          if (xhr.readyState == XMLHttpRequest.DONE) {
-            resolve('success');
-          }
-        }
-
-        xhr.open('GET', $1);
-        xhr.deprecatedBrowsingTopics = false;
-        xhr.send();
-      });)",
-                                                   xhr_url)));
-
-    absl::optional<std::string> topics_header_value =
-        GetTopicsHeaderForRequestPath(
-            "/browsing_topics/page_with_custom_topics_header.html");
-
-    // Expect no topics header as xhr.deprecatedBrowsingTopics was false.
-    EXPECT_FALSE(topics_header_value);
-  }
-}
-
-// On an insecure site (i.e. URL with http scheme), test XHR request that
-// attempts to set their `deprecatedBrowsingTopics` to true. Expect that the
-// request is not eligible for topics.
-IN_PROC_BROWSER_TEST_F(
-    BrowsingTopicsBrowserTest,
-    XhrCrossOrigin_TopicsNotEligibleDueToInsecureInitiatorContext) {
-  GURL main_frame_url = embedded_test_server()->GetURL(
-      "b.test", "/browsing_topics/empty_page.html");
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
-
-  GURL xhr_url = https_server_.GetURL(
-      "b.test", "/browsing_topics/page_with_custom_topics_header.html");
-
-  EXPECT_EQ("success", EvalJs(web_contents()->GetPrimaryMainFrame(),
-                              content::JsReplace(R"(
-    const xhr = new XMLHttpRequest();
-
-    new Promise(resolve => {
-      xhr.onreadystatechange = function() {
-        if (xhr.readyState == XMLHttpRequest.DONE) {
-          resolve('success');
-        }
-      }
-
-      xhr.open('GET', $1);
-
-      // This will no-op.
-      xhr.deprecatedBrowsingTopics = true;
-
-      xhr.send();
-    });)",
-                                                 xhr_url)));
-
-  absl::optional<std::string> topics_header_value =
-      GetTopicsHeaderForRequestPath(
-          "/browsing_topics/page_with_custom_topics_header.html");
-
-  // Expect no topics header as the request was not eligible for topics due to
-  // insecure initiator context.
-  EXPECT_FALSE(topics_header_value);
-}
-
-IN_PROC_BROWSER_TEST_F(
-    BrowsingTopicsBrowserTest,
-    XhrCrossOrigin_TopicsEligible_SendTopics_HasObserveResponse) {
-  GURL main_frame_url =
-      https_server_.GetURL("b.test", "/browsing_topics/empty_page.html");
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
-
-  base::StringPairs replacement;
-  replacement.emplace_back(std::make_pair("{{STATUS}}", "200 OK"));
-  replacement.emplace_back(std::make_pair("{{OBSERVE_BROWSING_TOPICS_HEADER}}",
-                                          "Observe-Browsing-Topics: ?1"));
-  replacement.emplace_back(std::make_pair("{{REDIRECT_HEADER}}", ""));
-
-  GURL xhr_url = https_server_.GetURL(
-      "a.test", net::test_server::GetFilePathWithReplacements(
-                    "/browsing_topics/"
-                    "page_with_custom_topics_header.html",
-                    replacement));
-
-  EXPECT_EQ("success", EvalJs(web_contents()->GetPrimaryMainFrame(),
-                              content::JsReplace(R"(
-    const xhr = new XMLHttpRequest();
-
-    new Promise(resolve => {
-      xhr.onreadystatechange = function() {
-        if (xhr.readyState == XMLHttpRequest.DONE) {
-          resolve('success');
-        }
-      }
-
-      xhr.open('GET', $1);
-      xhr.deprecatedBrowsingTopics = true;
-      xhr.send();
-    });)",
-                                                 xhr_url)));
-
-  absl::optional<std::string> topics_header_value =
-      GetTopicsHeaderForRequestPath(
-          "/browsing_topics/page_with_custom_topics_header.html");
-
-  EXPECT_TRUE(topics_header_value);
-  EXPECT_EQ(*topics_header_value, kExpectedHeaderValueForSiteB);
-
-  // A new observation should have been recorded in addition to the pre-existing
-  // one, as the response had the `Observe-Browsing-Topics: ?1` header and the
-  // request was eligible for topics.
-  std::vector<ApiUsageContext> api_usage_contexts =
-      content::GetBrowsingTopicsApiUsage(browsing_topics_site_data_manager());
-  EXPECT_EQ(api_usage_contexts.size(), 2u);
-  EXPECT_EQ(
-      api_usage_contexts[0].hashed_main_frame_host,
-      HashMainFrameHostForStorage(https_server_.GetURL("b.test", "/").host()));
-  EXPECT_EQ(api_usage_contexts[0].hashed_context_domain,
-            GetHashedDomain("a.test"));
-  EXPECT_EQ(api_usage_contexts[1].hashed_main_frame_host,
-            HashMainFrameHostForStorage("foo1.com"));
-  EXPECT_EQ(api_usage_contexts[1].hashed_context_domain, HashedDomain(1));
-}
-
 IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, UseCounter_DocumentApi) {
   base::HistogramTester histogram_tester;
 
@@ -1950,77 +1784,6 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest, UseCounter_Xhr) {
-  base::HistogramTester histogram_tester;
-
-  GURL main_frame_url =
-      https_server_.GetURL("a.test", "/browsing_topics/empty_page.html");
-
-  GURL xhr_url = main_frame_url;
-
-  {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
-
-    // Send a XHR request with `deprecatedBrowsingTopics` set to false. Expect
-    // no `kTopicsAPIXhr` use counter.
-    EXPECT_EQ("success", EvalJs(web_contents()->GetPrimaryMainFrame(),
-                                content::JsReplace(R"(
-      const xhr = new XMLHttpRequest();
-
-      new Promise(resolve => {
-        xhr.onreadystatechange = function() {
-          if (xhr.readyState == XMLHttpRequest.DONE) {
-            resolve('success');
-          }
-        }
-
-        xhr.open('GET', $1);
-        xhr.deprecatedBrowsingTopics = false;
-        xhr.send();
-      });)",
-                                                   xhr_url)));
-
-    // Navigate away to flush use counters.
-    ASSERT_TRUE(
-        ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
-
-    histogram_tester.ExpectBucketCount("Blink.UseCounter.Features",
-                                       blink::mojom::WebFeature::kTopicsAPIXhr,
-                                       0);
-  }
-
-  {
-    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_frame_url));
-
-    // Send a XHR request with `deprecatedBrowsingTopics` set to false. Expect
-    // one `kTopicsAPIXhr` use counter.
-    EXPECT_EQ("success", EvalJs(web_contents()->GetPrimaryMainFrame(),
-                                content::JsReplace(R"(
-    const xhr = new XMLHttpRequest();
-
-    new Promise(resolve => {
-      xhr.onreadystatechange = function() {
-        if (xhr.readyState == XMLHttpRequest.DONE) {
-          resolve('success');
-        }
-      }
-
-      xhr.open('GET', $1);
-      xhr.deprecatedBrowsingTopics = true;
-      xhr.send();
-    });)",
-                                                   xhr_url)));
-
-    // Navigate away to flush use counters.
-    ASSERT_TRUE(
-        ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL)));
-
-    histogram_tester.ExpectBucketCount("Blink.UseCounter.Features",
-                                       blink::mojom::WebFeature::kTopicsAPIXhr,
-                                       1);
-  }
-}
-
 // For a page that contains a static <iframe> with a "browsingtopics"
 // attribute, the iframe navigation request should be eligible for topics.
 IN_PROC_BROWSER_TEST_F(BrowsingTopicsBrowserTest,
@@ -2316,7 +2079,7 @@
   AttestationBrowsingTopicsBrowserTest() {
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/
-        {blink::features::kBrowsingTopics, blink::features::kBrowsingTopicsXHR,
+        {blink::features::kBrowsingTopics,
          blink::features::kBrowsingTopicsBypassIPIsPubliclyRoutableCheck,
          features::kPrivacySandboxAdsAPIsOverride},
         /*disabled_features=*/{});
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index a0ce86b..b833f2c 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3246,7 +3246,7 @@
     content_settings::PageSpecificContentSettings::StorageAccessed(
         content_settings::mojom::ContentSettingsManager::StorageType::
             FILE_SYSTEM,
-        it.child_id, it.frame_routing_id, rfh->GetStorageKey(), !allow);
+        it, rfh->GetStorageKey(), !allow);
   }
   std::move(callback).Run(allow);
 }
@@ -8259,3 +8259,9 @@
   CHECK(prefs);
   media_prefs::PreferenceRankVideoDeviceInfos(*prefs, infos);
 }
+
+network::mojom::IpProtectionProxyBypassPolicy
+ChromeContentBrowserClient::GetIpProtectionProxyBypassPolicy() {
+  return network::mojom::IpProtectionProxyBypassPolicy::
+      kFirstPartyToTopLevelFrame;
+}
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 514a4030..ca39f86 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -974,6 +974,8 @@
   void PreferenceRankVideoDeviceInfos(
       content::BrowserContext* browser_context,
       blink::WebMediaDeviceInfoArray& infos) override;
+  network::mojom::IpProtectionProxyBypassPolicy
+  GetIpProtectionProxyBypassPolicy() override;
 
  protected:
   static bool HandleWebUI(GURL* url, content::BrowserContext* browser_context);
diff --git a/chrome/browser/content_settings/content_settings_browsertest.cc b/chrome/browser/content_settings/content_settings_browsertest.cc
index bc83c025..6b46fa2f 100644
--- a/chrome/browser/content_settings/content_settings_browsertest.cc
+++ b/chrome/browser/content_settings/content_settings_browsertest.cc
@@ -1165,11 +1165,10 @@
   content::CommitMessageDelayer delayer(
       web_contents, second_url,
       base::BindOnce([](content::RenderFrameHost* rfh) {
-        auto global_id = rfh->GetGlobalId();
+        auto global_frame_token = rfh->GetGlobalFrameToken();
         // Call ContentBlocked while the RFH is pending commit.
         PageSpecificContentSettings::ContentBlocked(
-            global_id.child_id, global_id.frame_routing_id,
-            ContentSettingsType::JAVASCRIPT);
+            global_frame_token, ContentSettingsType::JAVASCRIPT);
       }));
   ui_test_utils::NavigateToURLWithDisposition(
       browser(), second_url, WindowOpenDisposition::CURRENT_TAB,
diff --git a/chrome/browser/content_settings/content_settings_manager_delegate.cc b/chrome/browser/content_settings/content_settings_manager_delegate.cc
index 72a153e..6d95eb9 100644
--- a/chrome/browser/content_settings/content_settings_manager_delegate.cc
+++ b/chrome/browser/content_settings/content_settings_manager_delegate.cc
@@ -25,34 +25,32 @@
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 void OnFileSystemAccessedInGuestViewContinuation(
-    int render_process_id,
-    int render_frame_id,
+    const content::GlobalRenderFrameHostToken& frame_token,
     const GURL& url,
     base::OnceCallback<void(bool)> callback,
     bool allowed) {
-  auto* rfh =
-      content::RenderFrameHost::FromID(render_process_id, render_frame_id);
+  auto* rfh = content::RenderFrameHost::FromFrameToken(frame_token);
   if (rfh) {
     content_settings::PageSpecificContentSettings::StorageAccessed(
         content_settings::mojom::ContentSettingsManager::StorageType::
             FILE_SYSTEM,
-        render_process_id, render_frame_id, rfh->GetStorageKey(), !allowed);
+        frame_token, rfh->GetStorageKey(), !allowed);
   }
 
   std::move(callback).Run(allowed);
 }
 
-void OnFileSystemAccessedInGuestView(int render_process_id,
-                                     int render_frame_id,
-                                     const GURL& url,
-                                     bool allowed,
-                                     base::OnceCallback<void(bool)> callback) {
+void OnFileSystemAccessedInGuestView(
+    const content::GlobalRenderFrameHostToken& frame_token,
+    const GURL& url,
+    bool allowed,
+    base::OnceCallback<void(bool)> callback) {
   extensions::WebViewPermissionHelper* web_view_permission_helper =
-      extensions::WebViewPermissionHelper::FromRenderFrameHostId(
-          content::GlobalRenderFrameHostId(render_process_id, render_frame_id));
-  auto continuation = base::BindOnce(
-      &OnFileSystemAccessedInGuestViewContinuation, render_process_id,
-      render_frame_id, url, std::move(callback));
+      extensions::WebViewPermissionHelper::FromRenderFrameHost(
+          content::RenderFrameHost::FromFrameToken(frame_token));
+  auto continuation =
+      base::BindOnce(&OnFileSystemAccessedInGuestViewContinuation, frame_token,
+                     url, std::move(callback));
   if (!web_view_permission_helper) {
     std::move(continuation).Run(allowed);
     return;
@@ -82,8 +80,7 @@
 }
 
 bool ContentSettingsManagerDelegate::AllowStorageAccess(
-    int render_process_id,
-    int render_frame_id,
+    const content::GlobalRenderFrameHostToken& frame_token,
     content_settings::mojom::ContentSettingsManager::StorageType storage_type,
     const GURL& url,
     bool allowed,
@@ -92,12 +89,11 @@
   if (storage_type == content_settings::mojom::ContentSettingsManager::
                           StorageType::FILE_SYSTEM &&
       extensions::WebViewRendererState::GetInstance()->IsGuest(
-          render_process_id)) {
+          frame_token.child_id)) {
     content::GetUIThreadTaskRunner({})->PostTask(
         FROM_HERE,
         base::BindOnce(
-            &OnFileSystemAccessedInGuestView, render_process_id,
-            render_frame_id, url, allowed,
+            &OnFileSystemAccessedInGuestView, frame_token, url, allowed,
             base::BindOnce(&PostTaskOnSequence,
                            base::SequencedTaskRunner::GetCurrentDefault(),
                            std::move(*callback))));
diff --git a/chrome/browser/content_settings/content_settings_manager_delegate.h b/chrome/browser/content_settings/content_settings_manager_delegate.h
index 11443d74..7cec67fa 100644
--- a/chrome/browser/content_settings/content_settings_manager_delegate.h
+++ b/chrome/browser/content_settings/content_settings_manager_delegate.h
@@ -20,8 +20,7 @@
   scoped_refptr<content_settings::CookieSettings> GetCookieSettings(
       content::BrowserContext* browser_context) override;
   bool AllowStorageAccess(
-      int render_process_id,
-      int render_frame_id,
+      const content::GlobalRenderFrameHostToken& frame_token,
       content_settings::mojom::ContentSettingsManager::StorageType storage_type,
       const GURL& url,
       bool allowed,
diff --git a/chrome/browser/dips/dips_bounce_detector_browsertest.cc b/chrome/browser/dips/dips_bounce_detector_browsertest.cc
index 1b034cf..b61fd9a 100644
--- a/chrome/browser/dips/dips_bounce_detector_browsertest.cc
+++ b/chrome/browser/dips/dips_bounce_detector_browsertest.cc
@@ -16,6 +16,7 @@
 #include "base/strings/strcat.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
+#include "base/test/gmock_expected_support.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
@@ -1463,17 +1464,17 @@
   // Open a server-redirecting link in a new tab.
   GURL new_tab_url(embedded_test_server()->GetURL(
       "b.test", "/cross-site-with-cookie/c.test/title1.html"));
-  auto maybe_new_tab = OpenInNewTab(original_tab, new_tab_url);
-  ASSERT_TRUE(maybe_new_tab.has_value()) << maybe_new_tab.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * new_tab,
+                       OpenInNewTab(original_tab, new_tab_url));
 
   // Verify the tab is different from the original and at the correct URL.
-  EXPECT_NE(*maybe_new_tab, original_tab);
-  ASSERT_EQ((*maybe_new_tab)->GetLastCommittedURL(),
+  EXPECT_NE(new_tab, original_tab);
+  ASSERT_EQ(new_tab->GetLastCommittedURL(),
             embedded_test_server()->GetURL("c.test", "/title1.html"));
 
   std::vector<std::string> redirects;
   DIPSWebContentsObserver* tab_web_contents_observer =
-      DIPSWebContentsObserver::FromWebContents(*maybe_new_tab);
+      DIPSWebContentsObserver::FromWebContents(new_tab);
   tab_web_contents_observer->SetRedirectChainHandlerForTesting(
       base::BindRepeating(&AppendRedirects, &redirects));
 
@@ -1497,23 +1498,22 @@
 
   // Open link in a new tab.
   GURL new_tab_url(embedded_test_server()->GetURL("b.test", "/title1.html"));
-  auto maybe_new_tab = OpenInNewTab(original_tab, new_tab_url);
-  ASSERT_TRUE(maybe_new_tab.has_value()) << maybe_new_tab.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * new_tab,
+                       OpenInNewTab(original_tab, new_tab_url));
 
   // Verify the tab is different from the original and at the correct URL.
-  EXPECT_NE(original_tab, *maybe_new_tab);
-  ASSERT_EQ(new_tab_url, (*maybe_new_tab)->GetLastCommittedURL());
+  EXPECT_NE(original_tab, new_tab);
+  ASSERT_EQ(new_tab_url, new_tab->GetLastCommittedURL());
 
   std::vector<std::string> redirects;
   DIPSWebContentsObserver* tab_web_contents_observer =
-      DIPSWebContentsObserver::FromWebContents(*maybe_new_tab);
+      DIPSWebContentsObserver::FromWebContents(new_tab);
   tab_web_contents_observer->SetRedirectChainHandlerForTesting(
       base::BindRepeating(&AppendRedirects, &redirects));
 
   // Navigate without a click (i.e. by C-redirecting) to c.test.
   ASSERT_TRUE(content::NavigateToURLFromRendererWithoutUserGesture(
-      *maybe_new_tab,
-      embedded_test_server()->GetURL("c.test", "/title1.html")));
+      new_tab, embedded_test_server()->GetURL("c.test", "/title1.html")));
   EndRedirectChain();
 
   EXPECT_THAT(
@@ -1534,17 +1534,17 @@
   // Open a server-redirecting link in a new tab.
   GURL new_tab_url(embedded_test_server()->GetURL(
       "b.test", "/cross-site-with-cookie/c.test/title1.html"));
-  auto maybe_new_tab = OpenInNewTab(original_tab, new_tab_url);
-  ASSERT_TRUE(maybe_new_tab.has_value()) << maybe_new_tab.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * new_tab,
+                       OpenInNewTab(original_tab, new_tab_url));
 
   // Verify the tab is different from the original and at the correct URL.
-  EXPECT_NE(*maybe_new_tab, original_tab);
-  ASSERT_EQ((*maybe_new_tab)->GetLastCommittedURL(),
+  EXPECT_NE(new_tab, original_tab);
+  ASSERT_EQ(new_tab->GetLastCommittedURL(),
             embedded_test_server()->GetURL("c.test", "/title1.html"));
 
   std::vector<std::string> redirects;
   DIPSWebContentsObserver* tab_web_contents_observer =
-      DIPSWebContentsObserver::FromWebContents(*maybe_new_tab);
+      DIPSWebContentsObserver::FromWebContents(new_tab);
   tab_web_contents_observer->SetRedirectChainHandlerForTesting(
       base::BindRepeating(&AppendRedirects, &redirects));
 
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
index 2581f91..c1eace1 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
+++ b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
@@ -27,10 +27,8 @@
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "components/content_settings/core/common/content_settings.h"
-#include "components/content_settings/core/common/features.h"
 #include "components/keep_alive_registry/keep_alive_types.h"
 #include "components/keep_alive_registry/scoped_keep_alive.h"
-#include "components/permissions/features.h"
 #include "components/permissions/permission_manager.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/common/content_switches.h"
@@ -39,6 +37,7 @@
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/test_extension_registry_observer.h"
+#include "extensions/common/extension_features.h"
 #include "net/base/schemeful_site.h"
 
 #if BUILDFLAG(ENABLE_PLUGINS)
@@ -131,6 +130,10 @@
     EXPECT_EQ(CONTENT_SETTING_BLOCK,
               map->GetContentSetting(example_url, example_url,
                                      ContentSettingsType::ANTI_ABUSE));
+    EXPECT_EQ(
+        CONTENT_SETTING_ASK,
+        map->GetContentSetting(example_url, example_url,
+                               ContentSettingsType::CLIPBOARD_READ_WRITE));
 
     // Check content settings for www.google.com
     GURL url("http://www.google.com");
@@ -164,6 +167,12 @@
     EXPECT_EQ(
         CONTENT_SETTING_BLOCK,
         map->GetContentSetting(url, url, ContentSettingsType::ANTI_ABUSE));
+    EXPECT_EQ(base::FeatureList::IsEnabled(
+                  extensions_features::kApiContentSettingsClipboard)
+                  ? CONTENT_SETTING_BLOCK
+                  : CONTENT_SETTING_ASK,
+              map->GetContentSetting(
+                  url, url, ContentSettingsType::CLIPBOARD_READ_WRITE));
   }
 
   void CheckContentSettingsDefault() {
@@ -205,6 +214,9 @@
     EXPECT_EQ(
         CONTENT_SETTING_ALLOW,
         map->GetContentSetting(url, url, ContentSettingsType::ANTI_ABUSE));
+    EXPECT_EQ(CONTENT_SETTING_ASK,
+              map->GetContentSetting(
+                  url, url, ContentSettingsType::CLIPBOARD_READ_WRITE));
   }
 
   // Returns a snapshot of content settings for a given URL.
@@ -238,6 +250,8 @@
         url, url, ContentSettingsType::AUTOMATIC_DOWNLOADS));
     content_settings.push_back(
         map->GetContentSetting(url, url, ContentSettingsType::AUTOPLAY));
+    content_settings.push_back(map->GetContentSetting(
+        url, url, ContentSettingsType::CLIPBOARD_READ_WRITE));
     return content_settings;
   }
 
@@ -247,6 +261,29 @@
   std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;
 };
 
+class ExtensionContentSettingsApiTestWithClipboard
+    : public ExtensionContentSettingsApiTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  ExtensionContentSettingsApiTestWithClipboard()
+      : ExtensionContentSettingsApiTest() {
+    scoped_feature_list_.InitWithFeatureState(
+        extensions_features::kApiContentSettingsClipboard, GetParam());
+  }
+  ~ExtensionContentSettingsApiTestWithClipboard() override = default;
+  ExtensionContentSettingsApiTestWithClipboard(
+      const ExtensionContentSettingsApiTestWithClipboard&) = delete;
+  ExtensionContentSettingsApiTestWithClipboard& operator=(
+      const ExtensionContentSettingsApiTestWithClipboard&) = delete;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         ExtensionContentSettingsApiTestWithClipboard,
+                         ::testing::Bool());
+
 class ExtensionContentSettingsApiTestWithContextType
     : public ExtensionContentSettingsApiTest,
       public testing::WithParamInterface<ContextType> {
@@ -267,7 +304,7 @@
                          ExtensionContentSettingsApiTestWithContextType,
                          ::testing::Values(ContextType::kServiceWorker));
 
-IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest, Standard) {
+IN_PROC_BROWSER_TEST_P(ExtensionContentSettingsApiTestWithClipboard, Standard) {
   CheckContentSettingsDefault();
 
   static constexpr char kExtensionPath[] = "content_settings/standard";
diff --git a/chrome/browser/extensions/extension_garbage_collector.cc b/chrome/browser/extensions/extension_garbage_collector.cc
index 3616fff..b6cef176 100644
--- a/chrome/browser/extensions/extension_garbage_collector.cc
+++ b/chrome/browser/extensions/extension_garbage_collector.cc
@@ -258,7 +258,7 @@
   if (crx_installs_in_progress_ < 0) {
     // This can only happen if there is a mismatch in our begin/finish
     // accounting.
-    NOTREACHED();
+    DUMP_WILL_BE_NOTREACHED_NORETURN();
 
     // Don't let the count go negative to avoid garbage collecting when
     // an install is actually in progress.
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 3f42fbc6..b7d6fe91 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2473,7 +2473,7 @@
   {
     "name": "enable-disco-feed-endpoint",
     "owners": [ "tinazwang@chromium.org", "sczs@chromium.org" ],
-    "expiry_milestone": 120
+    "expiry_milestone": 130
   },
   {
     "name": "enable-discount-consent-v2",
@@ -4148,7 +4148,7 @@
   {
     "name": "feed-dynamic-colors",
     "owners": ["//chrome/android/feed/OWNERS", "chili@chromium.org" ],
-    "expiry_milestone": 120
+    "expiry_milestone": 130
   },
   {
     "name": "feed-experiment-tagging-ios",
@@ -4178,7 +4178,7 @@
   {
     "name": "feed-loading-placeholder",
     "owners": [ "//chrome/android/feed/OWNERS", "dewittj@chromium.org" ],
-    "expiry_milestone": 120
+    "expiry_milestone": 130
   },
   {
     "name": "feed-signed-out-view-demotion",
@@ -4188,7 +4188,7 @@
   {
     "name": "feed-sports-card",
     "owners": ["//components/feed/OWNERS", "jianli@chromium.org"],
-    "expiry_milestone": 125
+    "expiry_milestone": 130
   },
   {
     "name": "feed-v2-hearts",
@@ -4751,7 +4751,7 @@
   {
     "name": "info-card-acknowledgement-tracking",
     "owners": [ "//chrome/android/feed/OWNERS", "jianli@chromium.org" ],
-    "expiry_milestone": 120
+    "expiry_milestone": 130
   },
   {
     "name": "infobar-scroll-optimization",
@@ -8241,7 +8241,7 @@
   {
     "name": "web-share",
     "owners": [ "mhochk@microsoft.com", "ericwilligers@google.com", "hatalat@microsoft.com" ],
-    "expiry_milestone": 121
+    "expiry_milestone": 128
   },
   {
     "name": "web-sql-access",
diff --git a/chrome/browser/ip_protection/ip_protection_config_provider.cc b/chrome/browser/ip_protection/ip_protection_config_provider.cc
index 2b6cbbd..dd6c73973 100644
--- a/chrome/browser/ip_protection/ip_protection_config_provider.cc
+++ b/chrome/browser/ip_protection/ip_protection_config_provider.cc
@@ -114,9 +114,16 @@
           std::move(callback).Run(absl::nullopt);
           return;
         }
-        std::vector<std::string> proxy_list(
-            response->first_hop_hostnames().begin(),
-            response->first_hop_hostnames().end());
+        std::vector<std::vector<std::string>> proxy_list;
+        for (const auto& proxy_chain : response->proxy_chain()) {
+          std::vector<std::string> proxies = {proxy_chain.proxy_a()};
+          // TODO(crbug.com/1491092): Remove check once proxy_b is populated by
+          // Phosphor.
+          if (!proxy_chain.proxy_b().empty()) {
+            proxies.emplace_back(proxy_chain.proxy_b());
+          }
+          proxy_list.push_back(std::move(proxies));
+        }
         VLOG(2) << "IPATP::GetProxyList got proxy list of length "
                 << proxy_list.size();
         std::move(callback).Run(std::move(proxy_list));
diff --git a/chrome/browser/ip_protection/ip_protection_config_provider_unittest.cc b/chrome/browser/ip_protection/ip_protection_config_provider_unittest.cc
index 1426d05..e44c5651 100644
--- a/chrome/browser/ip_protection/ip_protection_config_provider_unittest.cc
+++ b/chrome/browser/ip_protection/ip_protection_config_provider_unittest.cc
@@ -117,7 +117,7 @@
 class MockIpProtectionConfigHttp : public IpProtectionConfigHttp {
  public:
   explicit MockIpProtectionConfigHttp(
-      absl::optional<std::vector<std::string>> proxy_list)
+      absl::optional<std::vector<std::vector<std::string>>> proxy_list)
       : IpProtectionConfigHttp(
             base::MakeRefCounted<network::TestSharedURLLoaderFactory>()),
         proxy_list_(proxy_list) {}
@@ -137,14 +137,17 @@
       return;
     }
     ip_protection::GetProxyConfigResponse response;
-    for (auto& hostname : *proxy_list_) {
-      response.add_first_hop_hostnames(hostname);
+    for (auto& hostnames : *proxy_list_) {
+      ip_protection::GetProxyConfigResponse_ProxyChain* proxyChain =
+          response.add_proxy_chain();
+      proxyChain->set_proxy_a(hostnames.size() > 0 ? hostnames.at(0) : "");
+      proxyChain->set_proxy_b(hostnames.size() > 1 ? hostnames.at(1) : "");
     }
     std::move(callback).Run(response);
   }
 
  private:
-  absl::optional<std::vector<std::string>> proxy_list_;
+  absl::optional<std::vector<std::vector<std::string>>> proxy_list_;
 };
 
 enum class PrimaryAccountBehavior {
@@ -668,20 +671,22 @@
 }
 
 TEST_F(IpProtectionConfigProviderTest, GetProxyList) {
-  std::vector<std::string> proxy_list = {"proxy1", "proxy2"};
+  std::vector<std::vector<std::string>> proxy_list = {{"proxy1"}, {"proxy2"}};
   getter_->SetUpForTesting(
       std::make_unique<MockIpProtectionConfigHttp>(proxy_list), bsa_.get());
 
-  base::test::TestFuture<const absl::optional<std::vector<std::string>>&>
+  base::test::TestFuture<
+      const absl::optional<std::vector<std::vector<std::string>>>&>
       proxy_list_future;
   getter_->GetProxyList(proxy_list_future.GetCallback());
   ASSERT_TRUE(proxy_list_future.Wait()) << "GetProxyList did not call back";
   EXPECT_THAT(proxy_list_future.Get(),
-              testing::Optional(testing::ElementsAre("proxy1", "proxy2")));
+              testing::Optional(testing::ElementsAreArray(proxy_list)));
 }
 
 TEST_F(IpProtectionConfigProviderTest, GetProxyListFailure) {
-  base::test::TestFuture<const absl::optional<std::vector<std::string>>&>
+  base::test::TestFuture<
+      const absl::optional<std::vector<std::vector<std::string>>>&>
       proxy_list_future;
   getter_->GetProxyList(proxy_list_future.GetCallback());
   ASSERT_TRUE(proxy_list_future.Wait()) << "GetProxyList did not call back";
diff --git a/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensMetrics.java b/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensMetrics.java
index d73e732..eab3474 100644
--- a/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensMetrics.java
+++ b/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensMetrics.java
@@ -61,7 +61,9 @@
             AmbientSearchEntryPoint.SPOTLIGHT, AmbientSearchEntryPoint.APP_ICON_LONG_PRESS,
             AmbientSearchEntryPoint.PLUS_BUTTON, AmbientSearchEntryPoint.WEB_SEARCH_BAR,
             AmbientSearchEntryPoint.COMPANION_REGION_SEARCH,
-            AmbientSearchEntryPoint.TRANSLATE_ONEBOX, AmbientSearchEntryPoint.NUM_ENTRIES})
+            AmbientSearchEntryPoint.TRANSLATE_ONEBOX,
+            AmbientSearchEntryPoint.INTENTS,
+            AmbientSearchEntryPoint.WEB_IMAGES_SEARCH_BAR, AmbientSearchEntryPoint.NUM_ENTRIES})
     @Retention(RetentionPolicy.SOURCE)
     public static @interface AmbientSearchEntryPoint {
         int CONTEXT_MENU_SEARCH_IMAGE_WITH_GOOGLE_LENS = 0;
@@ -79,7 +81,9 @@
         int WEB_SEARCH_BAR = 12;
         int COMPANION_REGION_SEARCH = 13;
         int TRANSLATE_ONEBOX = 14;
-        int NUM_ENTRIES = 15;
+        int INTENTS = 15;
+        int WEB_IMAGES_SEARCH_BAR = 16;
+        int NUM_ENTRIES = 17;
     }
 
     // Note: These values must match the CameraOpenEntryPoint enum in enums.xml.
@@ -89,6 +93,7 @@
             CameraOpenEntryPoint.KEYBOARD, CameraOpenEntryPoint.SPOTLIGHT,
             CameraOpenEntryPoint.APP_ICON_LONG_PRESS, CameraOpenEntryPoint.PLUS_BUTTON,
             CameraOpenEntryPoint.WEB_SEARCH_BAR, CameraOpenEntryPoint.TRANSLATE_ONEBOX,
+            CameraOpenEntryPoint.INTENTS, CameraOpenEntryPoint.WEB_IMAGES_SEARCH_BAR,
             CameraOpenEntryPoint.NUM_ENTRIES})
     @Retention(RetentionPolicy.SOURCE)
     public static @interface CameraOpenEntryPoint {
@@ -102,7 +107,9 @@
         int PLUS_BUTTON = 7;
         int WEB_SEARCH_BAR = 8;
         int TRANSLATE_ONEBOX = 9;
-        int NUM_ENTRIES = 10;
+        int INTENTS = 10;
+        int WEB_IMAGES_SEARCH_BAR = 11;
+        int NUM_ENTRIES = 12;
     }
 
     /**
diff --git a/chrome/browser/net/cookie_policy_browsertest.cc b/chrome/browser/net/cookie_policy_browsertest.cc
index 940f70b..705b02f 100644
--- a/chrome/browser/net/cookie_policy_browsertest.cc
+++ b/chrome/browser/net/cookie_policy_browsertest.cc
@@ -750,7 +750,8 @@
     if (StoragePartitioningEnabled()) {
       return {};
     }
-    return {net::features::kThirdPartyStoragePartitioning};
+    return {net::features::kThirdPartyStoragePartitioning,
+            content_settings::features::kTrackingProtection3pcd};
   }
 
   ContextType ContextType() const { return std::get<0>(GetParam()); }
@@ -864,7 +865,8 @@
     : public ThirdPartyPartitionedStorageAccessibilityTest {
  protected:
   std::vector<base::test::FeatureRef> DisabledFeatures() override {
-    return {net::features::kThirdPartyPartitionedStorageAllowedByDefault};
+    return {net::features::kThirdPartyPartitionedStorageAllowedByDefault,
+            content_settings::features::kTrackingProtection3pcd};
   }
 };
 
diff --git a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.cc
index 6a9bbb4..b45ef3a 100644
--- a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.cc
@@ -590,43 +590,40 @@
   }
 
   RecordNormalizedResponsivenessMetrics(
-      subframe_info.responsiveness_metrics_normalization
-          .GetNormalizedResponsivenessMetrics(),
-      builder);
+      subframe_info.responsiveness_metrics_normalization, builder);
   builder.Record(ukm::UkmRecorder::Get());
 }
 
 void AMPPageLoadMetricsObserver::RecordNormalizedResponsivenessMetrics(
-    const page_load_metrics::NormalizedResponsivenessMetrics&
-        normalized_responsiveness_metrics,
+    const page_load_metrics::ResponsivenessMetricsNormalization&
+        responsiveness_metrics_normalization,
     ukm::builders::AmpPageLoad& builder) {
   DCHECK(!GetDelegate().IsInPrerenderingBeforeActivationStart());
 
-  if (!normalized_responsiveness_metrics.num_user_interactions)
+  if (!responsiveness_metrics_normalization.num_user_interactions()) {
     return;
+  }
 
   const std::string histogram_suffix =
       current_main_frame_nav_info_->is_same_document_navigation
           ? ""
           : ".FullNavigation";
-  auto& max_event_durations =
-      normalized_responsiveness_metrics.normalized_max_event_durations;
 
   builder
       .SetSubFrame_InteractiveTiming_WorstUserInteractionLatency_MaxEventDuration2(
-          max_event_durations.worst_latency.InMilliseconds());
+          responsiveness_metrics_normalization.worst_latency()
+              .value()
+              .InMilliseconds());
   base::UmaHistogramCustomTimes(
       std::string(kHistogramPrefix)
           .append(
               kHistogramAMPSubframeWorstUserInteractionLatencyMaxEventDuration)
           .append(histogram_suffix),
-      max_event_durations.worst_latency, base::Milliseconds(1),
-      base::Seconds(60), 50);
+      responsiveness_metrics_normalization.worst_latency().value(),
+      base::Milliseconds(1), base::Seconds(60), 50);
 
-  base::TimeDelta high_percentile2_max_event_duration = page_load_metrics::
-      ResponsivenessMetricsNormalization::ApproximateHighPercentile(
-          normalized_responsiveness_metrics.num_user_interactions,
-          max_event_durations.worst_ten_latencies);
+  base::TimeDelta high_percentile2_max_event_duration =
+      responsiveness_metrics_normalization.ApproximateHighPercentile().value();
 
   builder
       .SetSubFrame_InteractiveTiming_UserInteractionLatency_HighPercentile2_MaxEventDuration(
@@ -634,7 +631,7 @@
 
   builder.SetSubFrame_InteractiveTiming_NumInteractions(
       ukm::GetExponentialBucketMinForCounts1000(
-          normalized_responsiveness_metrics.num_user_interactions));
+          responsiveness_metrics_normalization.num_user_interactions()));
 
   base::UmaHistogramCustomTimes(
       std::string(kHistogramPrefix)
@@ -646,5 +643,5 @@
   base::UmaHistogramCounts1000(
       std::string(kHistogramPrefix)
           .append(kHistogramAMPSubframeNumInteractions),
-      normalized_responsiveness_metrics.num_user_interactions);
+      responsiveness_metrics_normalization.num_user_interactions());
 }
diff --git a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h
index 86db418..33dfc39 100644
--- a/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h
+++ b/chrome/browser/page_load_metrics/observers/core/amp_page_load_metrics_observer.h
@@ -137,8 +137,8 @@
 
   void MaybeRecordLoadingBehaviorObserved();
   void RecordNormalizedResponsivenessMetrics(
-      const page_load_metrics::NormalizedResponsivenessMetrics&
-          normalized_responsiveness_metrics,
+      const page_load_metrics::ResponsivenessMetricsNormalization&
+          responsiveness_metrics_normalization,
       ukm::builders::AmpPageLoad& builder);
   void ProcessMainFrameNavigation(content::NavigationHandle* navigation_handle);
   void MaybeRecordAmpDocumentMetrics();
diff --git a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
index 4ef9c5a..6e6bcec5 100644
--- a/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
+++ b/chrome/browser/page_load_metrics/observers/core/ukm_page_load_metrics_observer.cc
@@ -689,25 +689,22 @@
     }
   }
 
-  const page_load_metrics::NormalizedResponsivenessMetrics&
-      soft_nav_normalized_responsiveness_metrics =
+  const page_load_metrics::ResponsivenessMetricsNormalization&
+      soft_nav_responsiveness_metrics_normalization =
           GetDelegate()
-              .GetSoftNavigationIntervalNormalizedResponsivenessMetrics();
+              .GetSoftNavigationIntervalResponsivenessMetricsNormalization();
 
-  const auto& max_event_durations =
-      soft_nav_normalized_responsiveness_metrics.normalized_max_event_durations;
-  if (soft_nav_normalized_responsiveness_metrics.num_user_interactions) {
+  if (soft_nav_responsiveness_metrics_normalization.num_user_interactions()) {
     builder
         .SetInteractiveTiming_UserInteractionLatency_HighPercentile2_MaxEventDuration(
-            page_load_metrics::ResponsivenessMetricsNormalization::
-                ApproximateHighPercentile(
-                    soft_nav_normalized_responsiveness_metrics
-                        .num_user_interactions,
-                    max_event_durations.worst_ten_latencies)
-                    .InMilliseconds());
+            soft_nav_responsiveness_metrics_normalization
+                .ApproximateHighPercentile()
+                .value()
+                .InMilliseconds());
     builder.SetInteractiveTiming_NumInteractions(
         ukm::GetExponentialBucketMinForCounts1000(
-            soft_nav_normalized_responsiveness_metrics.num_user_interactions));
+            soft_nav_responsiveness_metrics_normalization
+                .num_user_interactions()));
   }
 
   // Don't report CLS if we were never in the foreground.
@@ -727,25 +724,22 @@
 void UkmPageLoadMetricsObserver::
     RecordResponsivenessMetricsBeforeSoftNavigationForMainFrame() {
   ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
-  const page_load_metrics::NormalizedResponsivenessMetrics&
-      normalized_responsiveness_metrics_before_soft_nav =
+  const page_load_metrics::ResponsivenessMetricsNormalization&
+      responsiveness_metrics_normalization_before_soft_nav =
           GetDelegate()
-              .GetSoftNavigationIntervalNormalizedResponsivenessMetrics();
-  auto& max_event_durations = normalized_responsiveness_metrics_before_soft_nav
-                                  .normalized_max_event_durations;
-  if (normalized_responsiveness_metrics_before_soft_nav.num_user_interactions) {
+              .GetSoftNavigationIntervalResponsivenessMetricsNormalization();
+  if (responsiveness_metrics_normalization_before_soft_nav
+          .num_user_interactions()) {
     builder
         .SetInteractiveTimingBeforeSoftNavigation_UserInteractionLatency_HighPercentile2_MaxEventDuration(
-            page_load_metrics::ResponsivenessMetricsNormalization::
-                ApproximateHighPercentile(
-                    normalized_responsiveness_metrics_before_soft_nav
-                        .num_user_interactions,
-                    max_event_durations.worst_ten_latencies)
-                    .InMilliseconds());
+            responsiveness_metrics_normalization_before_soft_nav
+                .ApproximateHighPercentile()
+                .value()
+                .InMilliseconds());
     builder.SetInteractiveTimingBeforeSoftNavigation_NumInteractions(
         ukm::GetExponentialBucketMinForCounts1000(
-            normalized_responsiveness_metrics_before_soft_nav
-                .num_user_interactions));
+            responsiveness_metrics_normalization_before_soft_nav
+                .num_user_interactions()));
   }
   builder.Record(ukm::UkmRecorder::Get());
 }
@@ -1356,28 +1350,22 @@
   DCHECK(!last_time_shown_.is_null());
 
   ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
-  const page_load_metrics::NormalizedResponsivenessMetrics&
-      normalized_responsiveness_metrics =
-          GetDelegate().GetNormalizedResponsivenessMetrics();
-  auto& max_event_durations =
-      normalized_responsiveness_metrics.normalized_max_event_durations;
-  if (normalized_responsiveness_metrics.num_user_interactions) {
+  const page_load_metrics::ResponsivenessMetricsNormalization&
+      responsiveness_metrics_normalization =
+          GetDelegate().GetResponsivenessMetricsNormalization();
+  if (responsiveness_metrics_normalization.num_user_interactions()) {
     builder
         .SetInteractiveTiming_UserInteractionLatencyAtFirstOnHidden_HighPercentile2_MaxEventDuration(
-            page_load_metrics::ResponsivenessMetricsNormalization::
-                ApproximateHighPercentile(
-                    normalized_responsiveness_metrics.num_user_interactions,
-                    max_event_durations.worst_ten_latencies)
-                    .InMilliseconds());
+            responsiveness_metrics_normalization.ApproximateHighPercentile()
+                .value()
+                .InMilliseconds());
 
     UmaHistogramCustomTimes(
         "PageLoad.InteractiveTiming.UserInteractionLatencyAtFirstOnHidden."
         "HighPercentile2."
         "MaxEventDuration",
-        page_load_metrics::ResponsivenessMetricsNormalization::
-            ApproximateHighPercentile(
-                normalized_responsiveness_metrics.num_user_interactions,
-                max_event_durations.worst_ten_latencies),
+        responsiveness_metrics_normalization.ApproximateHighPercentile()
+            .value(),
         base::Milliseconds(1), base::Seconds(60), 50);
   }
   builder.Record(ukm::UkmRecorder::Get());
@@ -1567,24 +1555,22 @@
 
 void UkmPageLoadMetricsObserver::RecordResponsivenessMetrics() {
   ukm::builders::PageLoad builder(GetDelegate().GetPageUkmSourceId());
-  const page_load_metrics::NormalizedResponsivenessMetrics&
-      normalized_responsiveness_metrics =
-          GetDelegate().GetNormalizedResponsivenessMetrics();
-  auto& max_event_durations =
-      normalized_responsiveness_metrics.normalized_max_event_durations;
-  if (normalized_responsiveness_metrics.num_user_interactions) {
+  const page_load_metrics::ResponsivenessMetricsNormalization&
+      responsiveness_metrics_normalization =
+          GetDelegate().GetResponsivenessMetricsNormalization();
+  if (responsiveness_metrics_normalization.num_user_interactions()) {
     builder.SetInteractiveTiming_WorstUserInteractionLatency_MaxEventDuration(
-        max_event_durations.worst_latency.InMilliseconds());
+        responsiveness_metrics_normalization.worst_latency()
+            .value()
+            .InMilliseconds());
     builder
         .SetInteractiveTiming_UserInteractionLatency_HighPercentile2_MaxEventDuration(
-            page_load_metrics::ResponsivenessMetricsNormalization::
-                ApproximateHighPercentile(
-                    normalized_responsiveness_metrics.num_user_interactions,
-                    max_event_durations.worst_ten_latencies)
-                    .InMilliseconds());
+            responsiveness_metrics_normalization.ApproximateHighPercentile()
+                .value()
+                .InMilliseconds());
     builder.SetInteractiveTiming_NumInteractions(
         ukm::GetExponentialBucketMinForCounts1000(
-            normalized_responsiveness_metrics.num_user_interactions));
+            responsiveness_metrics_normalization.num_user_interactions()));
   }
   builder.Record(ukm::UkmRecorder::Get());
 }
diff --git a/chrome/browser/page_load_metrics/observers/third_party_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/third_party_metrics_observer_browsertest.cc
index c0f208c..bb87d4d 100644
--- a/chrome/browser/page_load_metrics/observers/third_party_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/third_party_metrics_observer_browsertest.cc
@@ -3,9 +3,11 @@
 // found in the LICENSE file.
 
 #include "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/page_load_metrics/browser/observers/third_party_metrics_observer.h"
 #include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
@@ -174,6 +176,11 @@
     EXPECT_TRUE(ExecJs(ad_frame, no_op_script));
   }
 
+  void Enable3pcForUrl(GURL url) {
+    CookieSettingsFactory::GetForProfile(browser()->profile())
+        ->SetCookieSetting(url, CONTENT_SETTING_ALLOW);
+  }
+
   content::WebContents* web_contents() {
     return browser()->tab_strip_model()->GetActiveWebContents();
   }
@@ -308,6 +315,7 @@
 
 IN_PROC_BROWSER_TEST_F(ThirdPartyMetricsObserverBrowserTest,
                        ThirdPartyCookiesReadAndWrite) {
+  Enable3pcForUrl(https_server()->GetURL("b.com", "/"));
   content::CookieChangeObserver observer(web_contents(), 2);
   base::HistogramTester histogram_tester;
   NavigateToPageWithFrame("a.com");  // Same origin cookie read.
@@ -332,11 +340,12 @@
 
 IN_PROC_BROWSER_TEST_F(ThirdPartyMetricsObserverBrowserTest,
                        ThirdPartyCookiesIPAddress) {
+  GURL url =
+      https_server()->GetURL("/set-cookie?thirdparty=1;SameSite=None;Secure");
+  Enable3pcForUrl(url);
   content::CookieChangeObserver observer(web_contents(), 2);
   base::HistogramTester histogram_tester;
   NavigateToPageWithFrame("a.com");  // Same origin cookie read.
-  GURL url =
-      https_server()->GetURL("/set-cookie?thirdparty=1;SameSite=None;Secure");
   // Hostname is an IP address.
   ASSERT_EQ(
       "",
@@ -361,6 +370,8 @@
 
 IN_PROC_BROWSER_TEST_F(ThirdPartyMetricsObserverBrowserTest,
                        MultipleThirdPartyCookiesReadAndWrite) {
+  Enable3pcForUrl(https_server()->GetURL("b.com", "/"));
+  Enable3pcForUrl(https_server()->GetURL("c.com", "/"));
   content::CookieChangeObserver observer(web_contents(), 4);
   base::HistogramTester histogram_tester;
   NavigateToPageWithFrame("a.com");  // Same origin cookie read.
@@ -416,6 +427,7 @@
 
 IN_PROC_BROWSER_TEST_F(ThirdPartyMetricsObserverBrowserTest,
                        ThirdPartyDocCookieReadAndWrite) {
+  Enable3pcForUrl(https_server()->GetURL("b.com", "/"));
   content::CookieChangeObserver observer(web_contents(), 2);
   base::HistogramTester histogram_tester;
   NavigateToPageWithFrame("a.com");  // Same origin cookie read.
@@ -446,6 +458,7 @@
 
 IN_PROC_BROWSER_TEST_F(ThirdPartyMetricsObserverBrowserTest,
                        ThirdPartyDocCookieReadNoWrite) {
+  Enable3pcForUrl(https_server()->GetURL("b.com", "/"));
   base::HistogramTester histogram_tester;
   NavigateToPageWithFrame("a.com");  // Same origin cookie read.
   NavigateFrameTo("b.com", "/empty.html");
@@ -471,6 +484,7 @@
 
 IN_PROC_BROWSER_TEST_F(ThirdPartyMetricsObserverBrowserTest,
                        ThirdPartyDocCookieWriteNoRead) {
+  Enable3pcForUrl(https_server()->GetURL("b.com", "/"));
   content::CookieChangeObserver observer(web_contents());
   base::HistogramTester histogram_tester;
   NavigateToPageWithFrame("a.com");  // Same origin cookie read.
@@ -587,6 +601,8 @@
 IN_PROC_BROWSER_TEST_F(
     ThirdPartyMetricsObserverBrowserTest,
     ThirdPartyFrameWithAccessAndActivationOnDifferentThirdParties) {
+  Enable3pcForUrl(https_server()->GetURL("b.com", "/"));
+  Enable3pcForUrl(https_server()->GetURL("c.com", "/"));
   base::HistogramTester histogram_tester;
   NavigateToPageWithFrame("a.com");
   NavigateFrameTo("b.com", "/");
@@ -607,6 +623,7 @@
 IN_PROC_BROWSER_TEST_F(
     ThirdPartyMetricsObserverBrowserTest,
     ThirdPartyFrameWithAccessAndActivationOnSameThirdParties) {
+  Enable3pcForUrl(https_server()->GetURL("b.com", "/"));
   base::HistogramTester histogram_tester;
   NavigateToPageWithFrame("a.com");
   NavigateFrameTo("b.com", "/set-cookie?thirdparty=1;SameSite=None;Secure");
diff --git a/chrome/browser/policy/extension_policy_browsertest.cc b/chrome/browser/policy/extension_policy_browsertest.cc
index 06596dab..7c0057c 100644
--- a/chrome/browser/policy/extension_policy_browsertest.cc
+++ b/chrome/browser/policy/extension_policy_browsertest.cc
@@ -169,8 +169,9 @@
          const net::test_server::HttpRequest& request)
           -> std::unique_ptr<net::test_server::HttpResponse> {
         GURL url = test_server->GetURL(request.relative_url);
-        if (url.path() != match_path)
+        if (url.path() != match_path) {
           return nullptr;
+        }
 
         std::string contents;
         CHECK(base::ReadFileToString(template_file, &contents));
@@ -648,8 +649,9 @@
 
  private:
   bool OnRequest(content::URLLoaderInterceptor::RequestParams* params) {
-    if (callback_ && callback_.Run(params))
+    if (callback_ && callback_.Run(params)) {
       return true;
+    }
     // Mock out requests to the Web Store.
     if (params->url_request.url.host() == "clients2.google.com" &&
         params->url_request.url.path() == "/service/update2/crx") {
@@ -729,7 +731,7 @@
  private:
   extensions::InstallStageTracker::Stage stage_ =
       extensions::InstallStageTracker::Stage::CREATED;
-  raw_ptr<const content::BrowserContext> context_;
+  raw_ptr<const content::BrowserContext> context_ = nullptr;
 };
 
 std::string GetUpdateManifestBody(const std::string& id,
@@ -1776,8 +1778,9 @@
     interceptor.set_interceptor_hook(base::BindLambdaForTesting(
         [&](content::URLLoaderInterceptor::RequestParams* params) {
           if (params->url_request.url.path() !=
-              "/extensions/good_v1_update_manifest.xml")
+              "/extensions/good_v1_update_manifest.xml") {
             return false;
+          }
           params->client->OnComplete(network::URLLoaderCompletionStatus(
               net::ERR_INTERNET_DISCONNECTED));
           return true;
@@ -1998,11 +2001,14 @@
   base::RunLoop first_update_extension_runloop;
   interceptor.set_interceptor_hook(base::BindLambdaForTesting(
       [&](content::URLLoaderInterceptor::RequestParams* params) {
-        if (params->url_request.url.host() != "update.extension")
+        if (params->url_request.url.host() != "update.extension") {
           return false;
+        }
 
-        if (!update_extension_count.IsZero() && !update_extension_count.IsOne())
+        if (!update_extension_count.IsZero() &&
+            !update_extension_count.IsOne()) {
           return false;
+        }
 
         if (update_extension_count.IsZero()) {
           content::URLLoaderInterceptor::WriteResponse(
@@ -2012,8 +2018,9 @@
               "chrome/test/data/extensions/good2_update_manifest.xml",
               params->client.get());
         }
-        if (update_extension_count.IsZero())
+        if (update_extension_count.IsZero()) {
           first_update_extension_runloop.Quit();
+        }
         update_extension_count.Increment();
         return true;
       }));
@@ -2269,8 +2276,9 @@
   web_app::WebAppTestInstallObserver install_observer(browser()->profile());
   absl::optional<webapps::AppId> app_id =
       registrar.FindAppWithUrlInScope(policy_app_url_);
-  if (!app_id)
+  if (!app_id) {
     app_id = install_observer.BeginListeningAndWait();
+  }
   EXPECT_EQ(policy_app_url_, registrar.GetAppStartUrl(*app_id));
 }
 
@@ -2301,8 +2309,9 @@
   web_app::WebAppTestInstallObserver install_observer(browser()->profile());
   absl::optional<webapps::AppId> app_id =
       registrar.FindAppWithUrlInScope(policy_app_url_);
-  if (!app_id)
+  if (!app_id) {
     app_id = install_observer.BeginListeningAndWait();
+  }
   EXPECT_EQ(policy_app_url_, registrar.GetAppStartUrl(*app_id));
 
   // We specifically don't expect the fallback name to be used for a PWA
@@ -2333,8 +2342,9 @@
   web_app::WebAppTestInstallObserver install_observer(browser()->profile());
   absl::optional<webapps::AppId> app_id =
       registrar.FindAppWithUrlInScope(policy_app_url_);
-  if (!app_id)
+  if (!app_id) {
     app_id = install_observer.BeginListeningAndWait();
+  }
   EXPECT_EQ(policy_app_url_, registrar.GetAppStartUrl(*app_id));
   EXPECT_NE(fallback_app_name_, registrar.GetAppShortName(*app_id));
 }
@@ -2362,8 +2372,9 @@
   web_app::WebAppTestInstallObserver install_observer(browser()->profile());
   absl::optional<webapps::AppId> app_id =
       registrar.FindAppWithUrlInScope(policy_app_url_);
-  if (!app_id)
+  if (!app_id) {
     app_id = install_observer.BeginListeningAndWait();
+  }
   EXPECT_EQ(policy_app_url_, registrar.GetAppStartUrl(*app_id));
   EXPECT_EQ(fallback_app_name_, registrar.GetAppShortName(*app_id));
 }
@@ -2396,8 +2407,9 @@
       browser()->profile());
   absl::optional<webapps::AppId> app_id =
       registrar.FindAppWithUrlInScope(policy_app_url_);
-  if (!app_id)
+  if (!app_id) {
     app_id = install_observer.BeginListeningAndWait();
+  }
   EXPECT_EQ(policy_app_url_, registrar.GetAppStartUrl(*app_id));
   EXPECT_EQ(fallback_app_name_, registrar.GetAppShortName(*app_id));
   ASSERT_TRUE(registrar
@@ -2460,6 +2472,16 @@
     registry2_ = CreateExtensionRegistry(profile2_);
   }
 
+  void TearDownOnMainThread() override {
+    registry2_ = nullptr;
+    registry1_ = nullptr;
+    service2_ = nullptr;
+    service1_ = nullptr;
+    profile2_ = nullptr;
+    profile1_ = nullptr;
+    PolicyTest::TearDownOnMainThread();
+  }
+
  protected:
   void SetTabSpecificPermissionsForURL(const extensions::Extension* extension,
                                        int tab_id,
@@ -2521,14 +2543,14 @@
   std::unique_ptr<extensions::ExtensionCacheFake> test_extension_cache1_;
   std::unique_ptr<extensions::ExtensionCacheFake> test_extension_cache2_;
   extensions::ScopedIgnoreContentVerifierForTest ignore_content_verifier_;
-  raw_ptr<Profile, DanglingUntriaged> profile1_;
-  raw_ptr<Profile, DanglingUntriaged> profile2_;
+  raw_ptr<Profile> profile1_ = nullptr;
+  raw_ptr<Profile> profile2_ = nullptr;
   MockConfigurationPolicyProvider profile1_policy_;
   MockConfigurationPolicyProvider profile2_policy_;
-  raw_ptr<extensions::ExtensionRegistry, DanglingUntriaged> registry1_;
-  raw_ptr<extensions::ExtensionRegistry, DanglingUntriaged> registry2_;
-  raw_ptr<extensions::ExtensionService, DanglingUntriaged> service1_;
-  raw_ptr<extensions::ExtensionService, DanglingUntriaged> service2_;
+  raw_ptr<extensions::ExtensionRegistry> registry1_ = nullptr;
+  raw_ptr<extensions::ExtensionRegistry> registry2_ = nullptr;
+  raw_ptr<extensions::ExtensionService> service1_ = nullptr;
+  raw_ptr<extensions::ExtensionService> service2_ = nullptr;
 };
 
 // Verifies that default policy host block/allow settings are applied as
diff --git a/chrome/browser/resources/ash/settings/device_page/input_device_settings_shared.css b/chrome/browser/resources/ash/settings/device_page/input_device_settings_shared.css
index fbecc3b..7bbde9b 100644
--- a/chrome/browser/resources/ash/settings/device_page/input_device_settings_shared.css
+++ b/chrome/browser/resources/ash/settings/device_page/input_device_settings_shared.css
@@ -39,14 +39,6 @@
   padding: 6px;
 }
 
-/* TODO(b/281116317) Remove once Jelly is launched */
-:host-context(body:not(.jelly-enabled)) .key-container {
-  font-family: 'Google Sans', Roboto, sans-serif;
-  font-size: 13px;
-  font-weight: 500;
-  line-height: 20px;
-}
-
 #keyLabel {
   padding-inline: 6px;
 }
diff --git a/chrome/browser/resources/ash/settings/device_page/keyboard_remap_modifier_key_row.html b/chrome/browser/resources/ash/settings/device_page/keyboard_remap_modifier_key_row.html
index a91d130d..043f82f4 100644
--- a/chrome/browser/resources/ash/settings/device_page/keyboard_remap_modifier_key_row.html
+++ b/chrome/browser/resources/ash/settings/device_page/keyboard_remap_modifier_key_row.html
@@ -10,13 +10,6 @@
   }
 
   :host([key-state='modifier-remapped']) .key-container {
-    background-color: var(--cros-highlight-color);
-    border: none;
-    box-shadow: 0 1px 1px var(--cros-highlight-color);
-  }
-
-  :host-context(body.jelly-enabled):host([key-state='modifier-remapped'])
-      .key-container {
     background-color: var(--cros-sys-highlight_shape);
     border: none;
     box-shadow: 0 1px 1px var(--cros-sys-highlight_shape);
diff --git a/chrome/browser/resources/ash/settings/internet_page/cellular_setup_dialog.html b/chrome/browser/resources/ash/settings/internet_page/cellular_setup_dialog.html
index 12fdb738..9cfae40 100644
--- a/chrome/browser/resources/ash/settings/internet_page/cellular_setup_dialog.html
+++ b/chrome/browser/resources/ash/settings/internet_page/cellular_setup_dialog.html
@@ -40,16 +40,16 @@
 <cr-dialog id="dialog">
   <div slot="title">
     <template is="dom-if"
-        if="[[shouldShowDialogTitle_(dialogTitle_)]]" restamp>
-      <div id="title">
-        [[dialogTitle_]]
+        if="[[shouldShowPsimBanner_(psimBanner_)]]" restamp>
+      <div id="psim-banner">
+        [[psimBanner_]]
       </div>
     </template>
     <div id="header">[[getDialogHeader_(dialogHeader_)]]</div>
   </div>
   <div slot="body">
     <cellular-setup
-        flow-title="{{dialogTitle_}}"
+        flow-psim-banner="{{psimBanner_}}"
         flow-header="{{dialogHeader_}}"
         delegate="[[delegate_]]"
         current-page-name="[[pageName]]">
diff --git a/chrome/browser/resources/ash/settings/internet_page/cellular_setup_dialog.ts b/chrome/browser/resources/ash/settings/internet_page/cellular_setup_dialog.ts
index 32cd6b6..6c918c6 100644
--- a/chrome/browser/resources/ash/settings/internet_page/cellular_setup_dialog.ts
+++ b/chrome/browser/resources/ash/settings/internet_page/cellular_setup_dialog.ts
@@ -47,7 +47,7 @@
 
       delegate_: Object,
 
-      dialogTitle_: {
+      psimBanner_: {
         type: String,
       },
 
@@ -60,7 +60,7 @@
   pageName: CellularSetupPageName;
   private delegate_: CellularSetupDelegate;
   private dialogHeader_: string;
-  private dialogTitle_: string;
+  private psimBanner_: string;
 
   constructor() {
     super();
@@ -84,8 +84,8 @@
     this.$.dialog.close();
   }
 
-  private shouldShowDialogTitle_(): boolean {
-    return !!this.dialogTitle_;
+  private shouldShowPsimBanner_(): boolean {
+    return !!this.psimBanner_;
   }
 
   private getDialogHeader_(): string {
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_app_permission_row.html b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_app_permission_row.html
index ccd7249c..4db7d98 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_app_permission_row.html
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_app_permission_row.html
@@ -6,6 +6,10 @@
     width: 100%;
   }
 
+  #container:hover[actionable] {
+    background-color: var(--cr-hover-background-color);
+  }
+
   #appData {
     align-items: center;
     display: flex;
@@ -24,7 +28,9 @@
     margin-inline-end: var(--cr-controlled-by-spacing);
   }
 </style>
-<div id="container" on-click="onPermissionRowClick_" actionable>
+<div id="container"
+    actionable$="[[!isPermissionManaged_]]"
+    on-click="onPermissionRowClick_">
   <div id="appData">
     <img id="appIcon" src="chrome://app-icon/[[app.id]]/64"
         alt="[[app.name]] app icon." aria-hidden="true">
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_camera_subpage.html b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_camera_subpage.html
index 0108668..0643801 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_camera_subpage.html
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_camera_subpage.html
@@ -1 +1,52 @@
-<div>Camera subpage</div>
+<style include="settings-shared">
+  .list-item:not(:last-of-type) {
+    border-bottom: var(--cr-separator-line);
+  }
+
+  #onOffText[on] {
+    color: var(--cros-sys-primary);
+  }
+
+  #accessStatusRow:hover[actionable] {
+    background-color: var(--cr-hover-background-color);
+  }
+</style>
+<div
+    id="accessStatusRow"
+    class="settings-box first"
+    actionable$="[[!shouldDisableCameraToggle_]]"
+    on-click="onAccessStatusRowClick_">
+  <div class="start settings-box-text" aria-hidden="true">
+    <div id="onOffText" on$="[[prefs.ash.user.camera_allowed.value]]">
+      [[computeOnOffText_(prefs.ash.user.camera_allowed.value)]]
+    </div>
+    <div id="onOffSubtext" class="secondary">
+      [[computeOnOffSubtext_(prefs.ash.user.camera_allowed.value)]]
+    </div>
+  </div>
+  <div id="cameraToggleWrapper">
+    <cr-toggle
+        id="cameraToggle"
+        checked="{{prefs.ash.user.camera_allowed.value}}"
+        disabled="[[shouldDisableCameraToggle_]]">
+    </cr-toggle>
+  </div>
+</div>
+<template is="dom-if" if="[[prefs.ash.user.camera_allowed.value]]" restamp>
+  <div id="cameraListSection" class="list-frame">
+    <template is="dom-if" if="[[isCameraListEmpty_]]" restamp>
+      <div id="noCameraText" class="list-item">
+        $i18n{noCameraConnectedText}
+      </div>
+    </template>
+    <template is="dom-if" if="[[!isCameraListEmpty_]]" restamp>
+      <template id="cameraList" is="dom-repeat"
+          items="[[connectedCameras_]]">
+        <div class="list-item">
+          [[item]]
+        </div>
+      </template>
+    </template>
+  </div>
+</template>
+<div class="hr"></div>
\ No newline at end of file
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_camera_subpage.ts b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_camera_subpage.ts
index 06e5d08..5221bc5 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_camera_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_camera_subpage.ts
@@ -8,11 +8,23 @@
  * state of the system camera access.
  */
 
+import {PrefsMixin} from 'chrome://resources/cr_components/settings_prefs/prefs_mixin.js';
+import {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {castExists} from '../assert_extras.js';
+
+import {MediaDevicesProxy} from './media_devices_proxy.js';
+import {PrivacyHubBrowserProxy, PrivacyHubBrowserProxyImpl} from './privacy_hub_browser_proxy.js';
 import {getTemplate} from './privacy_hub_camera_subpage.html.js';
 
-export class SettingsPrivacyHubCameraSubpage extends PolymerElement {
+const SettingsPrivacyHubCameraSubpageBase =
+    WebUiListenerMixin(I18nMixin(PrefsMixin(PolymerElement)));
+
+export class SettingsPrivacyHubCameraSubpage extends
+    SettingsPrivacyHubCameraSubpageBase {
   static get is() {
     return 'settings-privacy-hub-camera-subpage' as const;
   }
@@ -20,6 +32,106 @@
   static get template() {
     return getTemplate();
   }
+
+  static get properties() {
+    return {
+      connectedCameras_: {
+        type: Array,
+        value: [],
+      },
+
+      isCameraListEmpty_: {
+        type: Boolean,
+        computed: 'computeIsCameraListEmpty_(connectedCameras_)',
+      },
+
+      /**
+       * Tracks if the Chrome code wants the camera switch to be disabled.
+       */
+      cameraSwitchForceDisabled_: {
+        type: Boolean,
+        value: false,
+      },
+
+      shouldDisableCameraToggle_: {
+        type: Boolean,
+        computed: 'computeShouldDisableCameraToggle_(isCameraListEmpty_, ' +
+            'cameraSwitchForceDisabled_)',
+      },
+
+    };
+  }
+
+  private browserProxy_: PrivacyHubBrowserProxy;
+  private cameraSwitchForceDisabled_: boolean;
+  private connectedCameras_: string[];
+  private isCameraListEmpty_: boolean;
+  private shouldDisableCameraToggle_: boolean;
+
+  constructor() {
+    super();
+
+    this.browserProxy_ = PrivacyHubBrowserProxyImpl.getInstance();
+  }
+
+  override ready(): void {
+    super.ready();
+
+    this.addWebUiListener(
+        'force-disable-camera-switch', (disabled: boolean) => {
+          this.cameraSwitchForceDisabled_ = disabled;
+        });
+    this.browserProxy_.getInitialCameraSwitchForceDisabledState().then(
+        (disabled) => {
+          this.cameraSwitchForceDisabled_ = disabled;
+        });
+
+    this.updateCameraList_();
+    MediaDevicesProxy.getMediaDevices().addEventListener(
+        'devicechange', () => this.updateCameraList_());
+  }
+
+  private async updateCameraList_(): Promise<void> {
+    const connectedCameras: string[] = [];
+    const devices: MediaDeviceInfo[] =
+        await MediaDevicesProxy.getMediaDevices().enumerateDevices();
+
+    devices.forEach((device) => {
+      if (device.kind === 'videoinput') {
+        connectedCameras.push(device.label);
+      }
+    });
+
+    this.connectedCameras_ = connectedCameras;
+  }
+
+  private computeIsCameraListEmpty_(): boolean {
+    return this.connectedCameras_.length === 0;
+  }
+
+  private computeOnOffText_(): string {
+    const cameraAllowed = this.getPref<string>('ash.user.camera_allowed').value;
+    return cameraAllowed ? this.i18n('deviceOn') : this.i18n('deviceOff');
+  }
+
+  private computeOnOffSubtext_(): string {
+    const cameraAllowed = this.getPref<string>('ash.user.camera_allowed').value;
+    return cameraAllowed ? this.i18n('cameraToggleSubtext') :
+                           this.i18n('blockedForAllText');
+  }
+
+  private computeShouldDisableCameraToggle_(): boolean {
+    return this.cameraSwitchForceDisabled_ || this.isCameraListEmpty_;
+  }
+
+  private getCameraToggle_(): CrToggleElement {
+    return castExists(
+        this.shadowRoot!.querySelector<CrToggleElement>('#cameraToggle'));
+  }
+
+  private onAccessStatusRowClick_(): void {
+    this.getCameraToggle_().click();
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_microphone_subpage.ts b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_microphone_subpage.ts
index 25e5c5f..de57b4dc 100644
--- a/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_microphone_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_privacy_page/privacy_hub_microphone_subpage.ts
@@ -179,7 +179,7 @@
     const microphoneAllowed =
         this.getPref<string>('ash.user.microphone_allowed').value;
     return microphoneAllowed ? this.i18n('microphoneToggleSubtext') :
-                               'Blocked for all';
+                               this.i18n('blockedForAllText');
   }
 
   private computeShouldDisableMicrophoneToggle_(): boolean {
diff --git a/chrome/browser/resources/browsing_topics/browsing_topics_internals.html b/chrome/browser/resources/browsing_topics/browsing_topics_internals.html
index 73c2c7a..f65e000 100644
--- a/chrome/browser/resources/browsing_topics/browsing_topics_internals.html
+++ b/chrome/browser/resources/browsing_topics/browsing_topics_internals.html
@@ -69,7 +69,6 @@
       <div id="privacy-sandbox-settings3-enabled-div">PrivacySandboxSettings3: </div>
       <div id="override-privacy-sandbox-settings-local-testing-enabled-div">OverridePrivacySandboxSettingsLocalTesting: </div>
       <div id="browsing-topics-bypass-ip-is-publicly-routable-check-enabled-div">BrowsingTopicsBypassIPIsPubliclyRoutableCheck: </div>
-      <div id="browsing-topics-xhr-enabled-div">BrowsingTopicsXHR: </div>
       <div id="browsing-topics-document-api-enabled-div">BrowsingTopicsDocumentAPI: </div>
       <div id="config-version-div">Configuration version: </div>
       <div id="browsing-topics-parameters-enabled-div">BrowsingTopicsParameters: </div>
diff --git a/chrome/browser/resources/browsing_topics/browsing_topics_internals.ts b/chrome/browser/resources/browsing_topics/browsing_topics_internals.ts
index 15e64d6..0d04663 100644
--- a/chrome/browser/resources/browsing_topics/browsing_topics_internals.ts
+++ b/chrome/browser/resources/browsing_topics/browsing_topics_internals.ts
@@ -129,7 +129,6 @@
    'privacy-sandbox-settings3-enabled-div',
    'override-privacy-sandbox-settings-local-testing-enabled-div',
    'browsing-topics-bypass-ip-is-publicly-routable-check-enabled-div',
-   'browsing-topics-xhr-enabled-div',
    'browsing-topics-document-api-enabled-div',
    'browsing-topics-parameters-enabled-div']
       .forEach(id => {
diff --git a/chrome/browser/resources/chromeos/login/BUILD.gn b/chrome/browser/resources/chromeos/login/BUILD.gn
index c4c6367..0b6a2ba 100644
--- a/chrome/browser/resources/chromeos/login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/BUILD.gn
@@ -320,6 +320,7 @@
     "screens/osauth/apply_online_password.js",
     "screens/osauth/cryptohome_recovery.js",
     "screens/osauth/cryptohome_recovery_setup.js",
+    "screens/osauth/factor_setup_success.js",
     "screens/osauth/fingerprint_setup.js",
     "screens/osauth/gaia_password_changed.js",
     "screens/osauth/local_password_setup.js",
diff --git a/chrome/browser/resources/chromeos/login/debug/debug.js b/chrome/browser/resources/chromeos/login/debug/debug.js
index 52a40d3..1dd12b81 100644
--- a/chrome/browser/resources/chromeos/login/debug/debug.js
+++ b/chrome/browser/resources/chromeos/login/debug/debug.js
@@ -888,6 +888,26 @@
       kind: ScreenKind.ERROR,
     },
     {
+      id: 'factor-setup-success',
+      kind: ScreenKind.NORMAL,
+      states: [
+        {
+          id: 'local-set',
+          data: {
+            modifiedFactors: 'local',
+            changeMode: 'set',
+          },
+        },
+        {
+          id: 'local-update',
+          data: {
+            modifiedFactors: 'local',
+            changeMode: 'update',
+          },
+        },
+      ],
+    },
+    {
       id: 'saml-confirm-password',
       kind: ScreenKind.OTHER,
       suffix: 'SAML',
diff --git a/chrome/browser/resources/chromeos/login/screens.js b/chrome/browser/resources/chromeos/login/screens.js
index 97074c9..c820c31 100644
--- a/chrome/browser/resources/chromeos/login/screens.js
+++ b/chrome/browser/resources/chromeos/login/screens.js
@@ -49,6 +49,7 @@
 // COMMON SCREENS USED TO SET UP AUTHENTICATION
 import './screens/osauth/apply_online_password.js';
 import './screens/osauth/cryptohome_recovery_setup.js';
+import './screens/osauth/factor_setup_success.js';
 import './screens/osauth/fingerprint_setup.js';
 import './screens/osauth/local_password_setup.js';
 import './screens/osauth/osauth_error.js';
@@ -145,6 +146,7 @@
   },
   {tag: 'oobe-reset-element', id: 'reset'},
   {tag: 'osauth-error-element', id: 'osauth-error'},
+  {tag: 'factor-setup-success-element', id: 'factor-setup-success'},
   {
     tag: 'os-install-element',
     id: 'os-install',
diff --git a/chrome/browser/resources/chromeos/login/screens/osauth/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/osauth/BUILD.gn
index c5b02536..f400d97 100644
--- a/chrome/browser/resources/chromeos/login/screens/osauth/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/screens/osauth/BUILD.gn
@@ -13,6 +13,7 @@
     ":apply_online_password",
     ":cryptohome_recovery",
     ":cryptohome_recovery_setup",
+    ":factor_setup_success",
     ":fingerprint_setup",
     ":gaia_password_changed",
     ":local_password_setup",
@@ -57,6 +58,19 @@
   extra_deps = [ ":web_components" ]
 }
 
+js_library("factor_setup_success") {
+  sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/osauth/factor_setup_success.js" ]
+  deps = [
+    "../../components:oobe_types",
+    "../../components/behaviors:login_screen_behavior",
+    "../../components/behaviors:oobe_dialog_host_behavior",
+    "../../components/behaviors:oobe_i18n_behavior",
+    "../../components/buttons:oobe_text_button",
+    "../../components/dialogs:oobe_adaptive_dialog",
+  ]
+  extra_deps = [ ":web_components" ]
+}
+
 js_library("fingerprint_setup") {
   sources = [ "$root_gen_dir/chrome/browser/resources/chromeos/login/screens/osauth/fingerprint_setup.js" ]
   deps = [
@@ -166,6 +180,7 @@
     "apply_online_password.js",
     "cryptohome_recovery.js",
     "cryptohome_recovery_setup.js",
+    "factor_setup_success.js",
     "fingerprint_setup.js",
     "gaia_password_changed.js",
     "local_password_setup.js",
diff --git a/chrome/browser/resources/chromeos/login/screens/osauth/factor_setup_success.html b/chrome/browser/resources/chromeos/login/screens/osauth/factor_setup_success.html
new file mode 100644
index 0000000..b315bc81
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/screens/osauth/factor_setup_success.html
@@ -0,0 +1,32 @@
+<!--
+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.
+-->
+
+<style include="oobe-dialog-host-styles">
+</style>
+<oobe-adaptive-dialog id="factorSetupSuccessDialog" role="dialog">
+  <iron-icon slot="icon" icon="oobe-32:lock"></iron-icon>
+  <h1 slot="title">
+    [[getTitle_(locale, factors_, changeMode_)]]
+  </h1>
+  <div slot="subtitle">
+    [[getSubtitle_(locale, factors_, changeMode_)]]
+  </div>
+  <div slot="content" class="flex layout vertical center center-justified">
+    <iron-icon icon="oobe-illos:pin-illustration-illo"
+        class="illustration-jelly">
+    </iron-icon>
+  </div>
+  <div slot="bottom-buttons">
+    <oobe-text-button id="doneButton" inverse on-click="onProceed_"
+        class="focus-on-show" text-key="factorSuccessDoneButton"
+        hidden="[[hasNextStep_]]">
+    </oobe-text-button>
+    <oobe-next-button id="nextButton" inverse on-click="onProceed_"
+        class="focus-on-show"
+     hidden="[[!hasNextStep_]]" >
+    </oobe-next-button>
+  </div>
+</oobe-adaptive-dialog>
diff --git a/chrome/browser/resources/chromeos/login/screens/osauth/factor_setup_success.js b/chrome/browser/resources/chromeos/login/screens/osauth/factor_setup_success.js
new file mode 100644
index 0000000..296c8bae
--- /dev/null
+++ b/chrome/browser/resources/chromeos/login/screens/osauth/factor_setup_success.js
@@ -0,0 +1,156 @@
+// 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.
+
+/**
+ * @fileoverview Polymer element for signin fatal error.
+ */
+
+import '//resources/js/action_link.js';
+import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '../../components/oobe_icons.html.js';
+import '../../components/common_styles/oobe_common_styles.css.js';
+import '../../components/common_styles/oobe_dialog_host_styles.css.js';
+import '../../components/dialogs/oobe_adaptive_dialog.js';
+
+import {html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {LoginScreenBehavior, LoginScreenBehaviorInterface} from '../../components/behaviors/login_screen_behavior.js';
+import {OobeDialogHostBehavior} from '../../components/behaviors/oobe_dialog_host_behavior.js';
+import {OobeI18nBehavior, OobeI18nBehaviorInterface} from '../../components/behaviors/oobe_i18n_behavior.js';
+import {OobeTextButton} from '../../components/buttons/oobe_text_button.js';
+import {OOBE_UI_STATE} from '../../components/display_manager_types.js';
+
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {LoginScreenBehaviorInterface}
+ * @implements {OobeI18nBehaviorInterface}
+ */
+const FactorSetupSuccessBase = mixinBehaviors(
+    [OobeI18nBehavior, OobeDialogHostBehavior, LoginScreenBehavior],
+    PolymerElement);
+
+/**
+ * @typedef {{
+ *   retryButton:  OobeTextButton,
+ * }}
+ */
+FactorSetupSuccessBase.$;
+
+// LINT.IfChange
+/**
+ * Set of modified factors, determine title/subtitle.
+ * @enum {string}
+ */
+const ModifiedFactors = {
+  ONLINE_PASSWORD: 'online',
+  LOCAL_PASSWORD: 'local',
+  ONLINE_PASSWORD_AND_PIN: 'online+pin',
+  LOCAL_PASSWORD_AND_PIN: 'local+pin',
+  PIN: 'pin',
+};
+
+/**
+ * Determines if factors were changed as a part of
+ * initial setup (set) or during recovery (updated)
+ * @enum {string}
+ */
+const ChangeMode = {
+  INITIAL_SETUP: 'set',
+  RECOVERY_FLOW: 'update',
+};
+
+
+const ACTION_PROCEED = 'proceed';
+// LINT.ThenChange(/chrome/browser/ash/login/screens/osauth/factor_setup_success_screen.cc)
+
+/**
+ * @polymer
+ */
+class FactorSetupSuccessScreen extends FactorSetupSuccessBase {
+  static get is() {
+    return 'factor-setup-success-element';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+
+  static get properties() {
+    return {
+      /**
+       * @private
+       */
+      hasNextStep_: {
+        type: Boolean,
+        value: true,
+      },
+      /**
+       * @private
+       */
+      factors_: {
+        type: String,
+        value: ModifiedFactors.ONLINE_PASSWORD,
+      },
+      /**
+       * @private
+       */
+      changeMode_: {
+        type: String,
+        value: ChangeMode.INITIAL_SETUP,
+      },
+    };
+  }
+
+  ready() {
+    super.ready();
+    this.initializeLoginScreen('FactorSetupSuccessScreen');
+  }
+
+  /** Initial UI State for screen */
+  getOobeUIInitialState() {
+    return OOBE_UI_STATE.BLOCKING;
+  }
+
+  /**
+   * Invoked just before being shown. Contains all the data for the screen.
+   */
+  onBeforeShow(data) {
+    this.factors_ = data['modifiedFactors'];
+    this.changeMode_ = data['changeMode'];
+    this.hasNextStep_ = this.changeMode_ === ChangeMode.INITIAL_SETUP;
+  }
+
+  getTitle_(locale, factors, changeMode) {
+    if (changeMode === ChangeMode.INITIAL_SETUP) {
+      if (factors === ModifiedFactors.LOCAL_PASSWORD) {
+        return this.i18n('factorSuccessTitleLocalPasswordSet');
+      }
+      // Add more strings here once we support more combinations of factors.
+      // Fallback option:
+      return this.i18n('factorSuccessTitleLocalPasswordSet');
+    } else {
+      if (factors === ModifiedFactors.LOCAL_PASSWORD) {
+        return this.i18n('factorSuccessTitleLocalPasswordUpdated');
+      }
+      // Add more strings here once we support more combinations of factors.
+      // Fallback option:
+      return this.i18n('factorSuccessTitleLocalPasswordUpdated');
+    }
+  }
+
+  getSubtitle_(locale, factors, changeMode) {
+    if (factors === ModifiedFactors.LOCAL_PASSWORD) {
+      return this.i18n('factorSuccessSubtitleLocalPassword');
+    }
+    return undefined;
+  }
+
+  onProceed_() {
+    this.userActed(ACTION_PROCEED);
+  }
+}
+
+customElements.define(FactorSetupSuccessScreen.is, FactorSetupSuccessScreen);
diff --git a/chrome/browser/resources/chromeos/login/screens/osauth/local_password_setup.html b/chrome/browser/resources/chromeos/login/screens/osauth/local_password_setup.html
index 824f0d9..c3a20b0 100644
--- a/chrome/browser/resources/chromeos/login/screens/osauth/local_password_setup.html
+++ b/chrome/browser/resources/chromeos/login/screens/osauth/local_password_setup.html
@@ -37,23 +37,3 @@
     title-key="gaiaLoading">
   <iron-icon slot="icon" icon="oobe-32:googleg"></iron-icon>
 </oobe-loading-dialog>
-<oobe-adaptive-dialog id="doneDialog" role="dialog" for-step="done"
-    footer-shrinkable>
-  <iron-icon slot="icon" icon="oobe-32:lock"></iron-icon>
-  <h1 slot="title">
-    [[doneTitleText_(locale, isRecoveryFlow)]]
-  </h1>
-  <div slot="subtitle">
-    [[i18nDynamic(locale, 'localPasswordSetupDoneSubtitle')]]</div>
-  <div slot="content" class="flex layout vertical center
-      center-justified">
-    <iron-icon icon="oobe-illos:pin-illustration-illo"
-        class="illustration-jelly">
-    </iron-icon>
-  </div>
-  <div slot="bottom-buttons">
-    <oobe-text-button id="doneButton" inverse on-click="onDoneClicked_"
-        class="focus-on-show" text-key="discoverPinSetupDone">
-    </oobe-text-button>
-  </div>
-</oobe-adaptive-dialog>
diff --git a/chrome/browser/resources/chromeos/login/screens/osauth/local_password_setup.js b/chrome/browser/resources/chromeos/login/screens/osauth/local_password_setup.js
index 6efc444..e5ae8a6 100644
--- a/chrome/browser/resources/chromeos/login/screens/osauth/local_password_setup.js
+++ b/chrome/browser/resources/chromeos/login/screens/osauth/local_password_setup.js
@@ -34,7 +34,6 @@
 const LocalPasswordSetupState = {
   PASSWORD: 'password',
   PROGRESS: 'progress',
-  DONE: 'done',
 };
 
 /**
@@ -120,10 +119,6 @@
     this.backButtonVisible_ = data['showBackButton'];
   }
 
-  showLocalPasswordSetupSuccess() {
-    this.setUIStep(LocalPasswordSetupState.DONE);
-  }
-
   showLocalPasswordSetupFailure() {
     // TODO(b/304963851): Show setup failed message, likely allowing user to
     // retry.
@@ -155,12 +150,6 @@
         isRecoveryFlow ? 'localPasswordResetTitle' : 'localPasswordSetupTitle';
     return this.i18n(key);
   }
-
-  doneTitleText_(locale, isRecoveryFlow) {
-    const key = isRecoveryFlow ? 'localPasswordResetDoneTitle' :
-                                 'localPasswordSetupDoneTitle';
-    return this.i18n(key);
-  }
 }
 
 customElements.define(LocalPasswordSetup.is, LocalPasswordSetup);
diff --git a/chrome/browser/resources/downloads/item.html b/chrome/browser/resources/downloads/item.html
index 48d40ce..7e1f4b3 100644
--- a/chrome/browser/resources/downloads/item.html
+++ b/chrome/browser/resources/downloads/item.html
@@ -124,7 +124,7 @@
   }
 
   #content:not(.is-active) .icon {
-    -webkit-filter: grayscale(100%);
+    filter: grayscale(100%);
     opacity: .5;
   }
 
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_service.cc b/chrome/browser/search_engine_choice/search_engine_choice_service.cc
index cb02f00..a8376868 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_service.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_service.cc
@@ -112,14 +112,22 @@
   browsers_with_open_dialogs_.clear();
 
   // Log the view entry point in which the choice was made.
-  if (entry_point == EntryPoint::kProfilePicker) {
-    choice_made_in_profile_picker_ = true;
-    search_engines::RecordChoiceScreenEvent(
-        search_engines::SearchEngineChoiceScreenEvents::kFreDefaultWasSet);
-  } else {
-    search_engines::RecordChoiceScreenEvent(
-        search_engines::SearchEngineChoiceScreenEvents::kDefaultWasSet);
+  search_engines::SearchEngineChoiceScreenEvents event;
+  switch (entry_point) {
+    case EntryPoint::kDialog:
+      event = search_engines::SearchEngineChoiceScreenEvents::kDefaultWasSet;
+      break;
+    case EntryPoint::kFirstRunExperience:
+      event = search_engines::SearchEngineChoiceScreenEvents::kFreDefaultWasSet;
+      choice_made_in_profile_picker_ = true;
+      break;
+    case EntryPoint::kProfileCreation:
+      event = search_engines::SearchEngineChoiceScreenEvents::
+          kProfileCreationDefaultWasSet;
+      choice_made_in_profile_picker_ = true;
+      break;
   }
+  search_engines::RecordChoiceScreenEvent(event);
 
   // `RecordChoiceMade` should always be called after setting the default
   // search engine.
@@ -274,9 +282,21 @@
 
 void SearchEngineChoiceService::NotifyLearnMoreLinkClicked(
     EntryPoint entry_point) {
-  RecordChoiceScreenEvent(entry_point == EntryPoint::kDialog
-                              ? search_engines::SearchEngineChoiceScreenEvents::
-                                    kLearnMoreWasDisplayed
-                              : search_engines::SearchEngineChoiceScreenEvents::
-                                    kFreLearnMoreWasDisplayed);
+  search_engines::SearchEngineChoiceScreenEvents event;
+
+  switch (entry_point) {
+    case EntryPoint::kDialog:
+      event = search_engines::SearchEngineChoiceScreenEvents::
+          kLearnMoreWasDisplayed;
+      break;
+    case EntryPoint::kFirstRunExperience:
+      event = search_engines::SearchEngineChoiceScreenEvents::
+          kFreLearnMoreWasDisplayed;
+      break;
+    case EntryPoint::kProfileCreation:
+      event = search_engines::SearchEngineChoiceScreenEvents::
+          kProfileCreationLearnMoreDisplayed;
+      break;
+  }
+  RecordChoiceScreenEvent(event);
 }
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_service.h b/chrome/browser/search_engine_choice/search_engine_choice_service.h
index ac575c7..9418d0c1 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_service.h
+++ b/chrome/browser/search_engine_choice/search_engine_choice_service.h
@@ -28,7 +28,8 @@
  public:
   // Specifies the view in which the choice screen UI is rendered.
   enum class EntryPoint {
-    kProfilePicker = 0,
+    kProfileCreation = 0,
+    kFirstRunExperience,
     kDialog,
   };
 
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_service_unittest.cc b/chrome/browser/search_engine_choice/search_engine_choice_service_unittest.cc
index ca13f8e..906bb90 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_service_unittest.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_service_unittest.cc
@@ -12,6 +12,7 @@
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "components/country_codes/country_codes.h"
+#include "components/search_engines/prepopulated_engines.h"
 #include "components/search_engines/search_engine_choice_utils.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/signin/public/base/signin_switches.h"
@@ -69,11 +70,19 @@
       1);
 
   search_engine_choice_service->NotifyLearnMoreLinkClicked(
-      SearchEngineChoiceService::EntryPoint::kProfilePicker);
+      SearchEngineChoiceService::EntryPoint::kFirstRunExperience);
   histogram_tester().ExpectBucketCount(
       search_engines::kSearchEngineChoiceScreenEventsHistogram,
       search_engines::SearchEngineChoiceScreenEvents::kFreLearnMoreWasDisplayed,
       1);
+
+  search_engine_choice_service->NotifyLearnMoreLinkClicked(
+      SearchEngineChoiceService::EntryPoint::kProfileCreation);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kSearchEngineChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::
+          kProfileCreationLearnMoreDisplayed,
+      1);
 }
 
 TEST_F(SearchEngineChoiceServiceTest, CanShowDialog) {
@@ -91,3 +100,31 @@
       search_engines::SearchEngineChoiceScreenConditions::kFeatureSuppressed,
       1);
 }
+
+TEST_F(SearchEngineChoiceServiceTest, NotifyChoiceMade) {
+  SearchEngineChoiceService* search_engine_choice_service =
+      SearchEngineChoiceServiceFactory::GetForProfile(profile());
+
+  search_engine_choice_service->NotifyChoiceMade(
+      TemplateURLPrepopulateData::google.id,
+      SearchEngineChoiceService::EntryPoint::kDialog);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kSearchEngineChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::kDefaultWasSet, 1);
+
+  search_engine_choice_service->NotifyChoiceMade(
+      TemplateURLPrepopulateData::google.id,
+      SearchEngineChoiceService::EntryPoint::kFirstRunExperience);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kSearchEngineChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::kFreDefaultWasSet, 1);
+
+  search_engine_choice_service->NotifyChoiceMade(
+      TemplateURLPrepopulateData::google.id,
+      SearchEngineChoiceService::EntryPoint::kProfileCreation);
+  histogram_tester().ExpectBucketCount(
+      search_engines::kSearchEngineChoiceScreenEventsHistogram,
+      search_engines::SearchEngineChoiceScreenEvents::
+          kProfileCreationDefaultWasSet,
+      1);
+}
diff --git a/chrome/browser/sharing_hub/sharing_hub_model.cc b/chrome/browser/sharing_hub/sharing_hub_model.cc
index fc4c8076..29bc509 100644
--- a/chrome/browser/sharing_hub/sharing_hub_model.cc
+++ b/chrome/browser/sharing_hub/sharing_hub_model.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/sharing_hub/sharing_hub_model.h"
 
 #include "base/base64.h"
+#include "base/check_deref.h"
 #include "base/logging.h"
 #include "base/metrics/user_metrics.h"
 #include "base/strings/escape.h"
@@ -48,7 +49,7 @@
                                    int announcement_id)
     : command_id(command_id),
       title(title),
-      icon(icon),
+      icon(CHECK_DEREF(icon)),
       feature_name_for_metrics(feature_name_for_metrics),
       announcement_id(announcement_id) {}
 
diff --git a/chrome/browser/sharing_hub/sharing_hub_model.h b/chrome/browser/sharing_hub/sharing_hub_model.h
index f3ac4453..9356545 100644
--- a/chrome/browser/sharing_hub/sharing_hub_model.h
+++ b/chrome/browser/sharing_hub/sharing_hub_model.h
@@ -9,7 +9,7 @@
 #include <string>
 #include <vector>
 
-#include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ref.h"
 #include "base/sequence_checker.h"
 #include "ui/gfx/image/image_skia.h"
 
@@ -28,6 +28,7 @@
 namespace sharing_hub {
 
 struct SharingHubAction {
+  // `icon` may not be null and must outlive the SharingHubAction.
   SharingHubAction(int command_id,
                    std::u16string title,
                    const gfx::VectorIcon* icon,
@@ -40,7 +41,7 @@
   ~SharingHubAction() = default;
   int command_id;
   std::u16string title;
-  raw_ptr<const gfx::VectorIcon> icon;
+  raw_ref<const gfx::VectorIcon> icon;
   std::string feature_name_for_metrics;
   int announcement_id;
 };
diff --git a/chrome/browser/supervised_user/supervised_user_url_filter_browsertest.cc b/chrome/browser/supervised_user/supervised_user_url_filter_browsertest.cc
index 8543a04..b1a62a9 100644
--- a/chrome/browser/supervised_user/supervised_user_url_filter_browsertest.cc
+++ b/chrome/browser/supervised_user/supervised_user_url_filter_browsertest.cc
@@ -11,6 +11,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/types/strong_alias.h"
 #include "base/values.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -62,7 +63,7 @@
 namespace {
 
 // Tests filtering for supervised users.
-class SupervisedUserURLFilterTest : public MixinBasedInProcessBrowserTest {
+class SupervisedUserURLFilterTestBase : public MixinBasedInProcessBrowserTest {
  public:
   // Indicates whether the interstitial should proceed or not.
   enum InterstitialAction {
@@ -70,7 +71,7 @@
     INTERSTITIAL_DONTPROCEED,
   };
 
-  SupervisedUserURLFilterTest() {
+  SupervisedUserURLFilterTestBase() {
     // TODO(crbug.com/1394910): Use HTTPS URLs in tests to avoid having to
     // disable this feature.
     feature_list_.InitWithFeatures(
@@ -78,13 +79,7 @@
          supervised_user::kSupervisedPrefsControlledBySupervisedStore},
         {features::kHttpsUpgrades});
   }
-  ~SupervisedUserURLFilterTest() override { feature_list_.Reset(); }
-
-  // TODO(crbug.com/1491942): This fails with the field trial testing config.
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
-    command_line->AppendSwitch("disable-field-trial-config");
-  }
+  ~SupervisedUserURLFilterTestBase() override { feature_list_.Reset(); }
 
   bool ShownPageIsInterstitial(Browser* browser) {
     WebContents* tab = browser->tab_strip_model()->GetActiveWebContents();
@@ -132,10 +127,15 @@
     run_loop.Run();  // Will go until ...Complete calls Quit.
   }
 
+  supervised_user::KidsManagementApiServerMock& kids_management_api_mock() {
+    return supervision_mixin_.api_mock_setup_mixin().api_mock();
+  }
+
   supervised_user::SupervisedUserService* GetSupervisedUserService() const {
     return SupervisedUserServiceFactory::GetForProfile(browser()->profile());
   }
 
+ private:
   base::test::ScopedFeatureList feature_list_;
   supervised_user::SupervisionMixin supervision_mixin_{
       mixin_host_,
@@ -150,23 +150,6 @@
       }};
 };
 
-// Tests the filter mode in which all sites are blocked by default.
-class SupervisedUserBlockModeTest : public SupervisedUserURLFilterTest {
- public:
-  void SetUpOnMainThread() override {
-    SupervisedUserURLFilterTest::SetUpOnMainThread();
-    Profile* profile = browser()->profile();
-    supervised_user::SupervisedUserSettingsService*
-        supervised_user_settings_service =
-            SupervisedUserSettingsServiceFactory::GetForKey(
-                profile->GetProfileKey());
-    supervised_user_settings_service->SetLocalSetting(
-        supervised_user::kContentPackDefaultFilteringBehavior,
-        base::Value(
-            static_cast<int>(supervised_user::FilteringBehavior::kBlock)));
-  }
-};
-
 class TabClosingObserver : public TabStripModelObserver {
  public:
   TabClosingObserver(TabStripModel* tab_strip, content::WebContents* contents)
@@ -213,61 +196,40 @@
   raw_ptr<content::WebContents> contents_;
 };
 
-// Navigates to a blocked URL.
-IN_PROC_BROWSER_TEST_F(SupervisedUserBlockModeTest,
-                       SendAccessRequestOnBlockedURL) {
-  GURL test_url("http://www.example.com/simple.html");
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
+using UseProtoFetcher = base::StrongAlias<class UseProtoFetcherTag, bool>;
+class SupervisedUserURLFilterTest
+    : public SupervisedUserURLFilterTestBase,
+      public ::testing::WithParamInterface<UseProtoFetcher> {
+ public:
+  SupervisedUserURLFilterTest() {
+    if (UseProtoFetcher()) {
+      proto_api_feature_list_.InitAndEnableFeature(
+          supervised_user::kEnableProtoApiForClassifyUrl);
+    } else {
+      proto_api_feature_list_.InitAndDisableFeature(
+          supervised_user::kEnableProtoApiForClassifyUrl);
+    }
+  }
 
-  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
-
-  ASSERT_TRUE(ShownPageIsInterstitial(browser()));
-
-  SendAccessRequest(tab);
-
-  // TODO(sergiu): Properly check that the access request was sent here.
-
-  GoBackAndWaitForNavigation(tab);
-
-  // Make sure that the tab is still there.
-  EXPECT_EQ(tab, browser()->tab_strip_model()->GetActiveWebContents());
-
-  EXPECT_FALSE(ShownPageIsInterstitial(browser()));
-}
-
-// Navigates to a blocked URL in a new tab. We expect the tab to be closed
-// automatically on pressing the "back" button on the interstitial.
-IN_PROC_BROWSER_TEST_F(SupervisedUserBlockModeTest, OpenBlockedURLInNewTab) {
-  TabStripModel* tab_strip = browser()->tab_strip_model();
-  WebContents* prev_tab = tab_strip->GetActiveWebContents();
-
-  // Open blocked URL in a new tab.
-  GURL test_url("http://www.example.com/simple.html");
-  ui_test_utils::NavigateToURLWithDisposition(
-      browser(), test_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
-      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
-
-  // Check that we got the interstitial.
-  WebContents* tab = tab_strip->GetActiveWebContents();
-  ASSERT_TRUE(ShownPageIsInterstitial(browser()));
-
-  // On pressing the "back" button, the new tab should be closed, and we should
-  // get back to the previous active tab.
-  TabClosingObserver observer(tab_strip, tab);
-  GoBack(tab);
-  observer.WaitForContentsClosing();
-
-  EXPECT_EQ(prev_tab, tab_strip->GetActiveWebContents());
-}
+ private:
+  bool UseProtoFetcher() const { return GetParam().value(); }
+  base::test::ScopedFeatureList proto_api_feature_list_;
+};
 
 // Navigates to a page in a new tab, then blocks it (which makes the
 // interstitial page behave differently from the preceding test, where the
 // navigation is blocked before it commits). The expected behavior is the same
 // though: the tab should be closed when going back.
-IN_PROC_BROWSER_TEST_F(SupervisedUserURLFilterTest, BlockNewTabAfterLoading) {
+IN_PROC_BROWSER_TEST_P(SupervisedUserURLFilterTest, BlockNewTabAfterLoading) {
   TabStripModel* tab_strip = browser()->tab_strip_model();
   WebContents* prev_tab = tab_strip->GetActiveWebContents();
 
+  if (supervised_user::IsProtoApiForClassifyUrlEnabled()) {
+    kids_management_api_mock().AllowSubsequentClassifyUrl();
+    EXPECT_CALL(kids_management_api_mock().classify_url_mock(), ClassifyUrl)
+        .Times(1);
+  }
+
   // Open URL in a new tab.
   GURL test_url("http://www.example.com/simple.html");
   ui_test_utils::NavigateToURLWithDisposition(
@@ -313,11 +275,17 @@
 
 // Tests that we don't end up canceling an interstitial (thereby closing the
 // whole tab) by attempting to show a second one above it.
-IN_PROC_BROWSER_TEST_F(SupervisedUserURLFilterTest, DontShowInterstitialTwice) {
+IN_PROC_BROWSER_TEST_P(SupervisedUserURLFilterTest, DontShowInterstitialTwice) {
   TabStripModel* tab_strip = browser()->tab_strip_model();
 
   // Open URL in a new tab.
   GURL test_url("http://www.example.com/simple.html");
+
+  if (supervised_user::IsProtoApiForClassifyUrlEnabled()) {
+    kids_management_api_mock().AllowSubsequentClassifyUrl();
+    EXPECT_CALL(kids_management_api_mock().classify_url_mock(), ClassifyUrl)
+        .Times(1);
+  }
   ui_test_utils::NavigateToURLWithDisposition(
       browser(), test_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
       ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
@@ -354,6 +322,165 @@
   EXPECT_EQ(tab, tab_strip->GetActiveWebContents());
 }
 
+IN_PROC_BROWSER_TEST_P(SupervisedUserURLFilterTest, GoBackOnDontProceed) {
+  WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  // Ensure navigation completes.
+  EXPECT_TRUE(WaitForLoadStop(web_contents));
+  // We start out at the initial navigation.
+  ASSERT_EQ(0, web_contents->GetController().GetCurrentEntryIndex());
+
+  GURL test_url("http://www.example.com/simple.html");
+  if (supervised_user::IsProtoApiForClassifyUrlEnabled()) {
+    kids_management_api_mock().AllowSubsequentClassifyUrl();
+    EXPECT_CALL(kids_management_api_mock().classify_url_mock(), ClassifyUrl)
+        .Times(1);
+  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
+
+  ASSERT_FALSE(ShownPageIsInterstitial(browser()));
+
+  // Set the host as blocked and wait for the interstitial to appear.
+  base::Value::Dict dict;
+  dict.Set(test_url.host(), false);
+  supervised_user::SupervisedUserSettingsService*
+      supervised_user_settings_service =
+          SupervisedUserSettingsServiceFactory::GetForKey(
+              browser()->profile()->GetProfileKey());
+  supervised_user_settings_service->SetLocalSetting(
+      supervised_user::kContentPackManualBehaviorHosts, std::move(dict));
+
+  supervised_user::SupervisedUserURLFilter* filter =
+      GetSupervisedUserService()->GetURLFilter();
+  ASSERT_EQ(supervised_user::FilteringBehavior::kBlock,
+            filter->GetFilteringBehaviorForURL(test_url));
+
+  content::TestNavigationObserver block_observer(web_contents);
+  block_observer.Wait();
+
+  content::LoadStopObserver observer(web_contents);
+  GoBack(web_contents);
+  observer.Wait();
+
+  // We should have gone back to the initial navigation.
+  EXPECT_EQ(0, web_contents->GetController().GetCurrentEntryIndex());
+}
+
+IN_PROC_BROWSER_TEST_P(SupervisedUserURLFilterTest,
+                       ClosingBlockedTabDoesNotCrash) {
+  WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  // Ensure navigation completes.
+  EXPECT_TRUE(WaitForLoadStop(web_contents));
+  ASSERT_EQ(0, web_contents->GetController().GetCurrentEntryIndex());
+
+  GURL test_url("http://www.example.com/simple.html");
+  if (supervised_user::IsProtoApiForClassifyUrlEnabled()) {
+    kids_management_api_mock().AllowSubsequentClassifyUrl();
+    EXPECT_CALL(kids_management_api_mock().classify_url_mock(), ClassifyUrl)
+        .Times(1);
+  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
+  ASSERT_FALSE(ShownPageIsInterstitial(browser()));
+
+  // Set the host as blocked and wait for the interstitial to appear.
+  base::Value::Dict dict;
+  dict.Set(test_url.host(), false);
+  supervised_user::SupervisedUserSettingsService*
+      supervised_user_settings_service =
+          SupervisedUserSettingsServiceFactory::GetForKey(
+              browser()->profile()->GetProfileKey());
+  supervised_user_settings_service->SetLocalSetting(
+      supervised_user::kContentPackManualBehaviorHosts, std::move(dict));
+
+  supervised_user::SupervisedUserURLFilter* filter =
+      GetSupervisedUserService()->GetURLFilter();
+  ASSERT_EQ(supervised_user::FilteringBehavior::kBlock,
+            filter->GetFilteringBehaviorForURL(test_url));
+
+  // Verify that there is no crash when closing the blocked tab
+  // (https://crbug.com/719708).
+  browser()->tab_strip_model()->CloseWebContentsAt(
+      0, TabCloseTypes::CLOSE_USER_GESTURE);
+}
+
+IN_PROC_BROWSER_TEST_P(SupervisedUserURLFilterTest, BlockThenUnblock) {
+  GURL test_url("http://www.example.com/simple.html");
+  if (supervised_user::IsProtoApiForClassifyUrlEnabled()) {
+    kids_management_api_mock().AllowSubsequentClassifyUrl();
+    EXPECT_CALL(kids_management_api_mock().classify_url_mock(), ClassifyUrl)
+        .Times(1);
+  }
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
+
+  WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  ASSERT_FALSE(ShownPageIsInterstitial(browser()));
+
+  supervised_user::SupervisedUserSettingsService*
+      supervised_user_settings_service =
+          SupervisedUserSettingsServiceFactory::GetForKey(
+              browser()->profile()->GetProfileKey());
+  // Set the host as blocked and wait for the interstitial to appear.
+  {
+    base::Value::Dict dict;
+    dict.Set(test_url.host(), false);
+    supervised_user_settings_service->SetLocalSetting(
+        supervised_user::kContentPackManualBehaviorHosts, std::move(dict));
+  }
+
+  supervised_user::SupervisedUserURLFilter* filter =
+      GetSupervisedUserService()->GetURLFilter();
+  ASSERT_EQ(supervised_user::FilteringBehavior::kBlock,
+            filter->GetFilteringBehaviorForURL(test_url));
+
+  content::TestNavigationObserver block_observer(web_contents);
+  block_observer.Wait();
+
+  ASSERT_TRUE(ShownPageIsInterstitial(browser()));
+  {
+    base::Value::Dict dict;
+    dict.Set(test_url.host(), true);
+    supervised_user_settings_service->SetLocalSetting(
+        supervised_user::kContentPackManualBehaviorHosts, std::move(dict));
+    ASSERT_EQ(supervised_user::FilteringBehavior::kAllow,
+              filter->GetFilteringBehaviorForURL(test_url));
+  }
+
+  content::TestNavigationObserver unblock_observer(web_contents);
+  unblock_observer.Wait();
+
+  ASSERT_EQ(test_url, web_contents->GetLastCommittedURL());
+
+  EXPECT_FALSE(ShownPageIsInterstitial(browser()));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SupervisedUserURLFilterTest,
+    ::testing::Values(UseProtoFetcher(true), UseProtoFetcher(false)),
+    [](const ::testing::TestParamInfo<UseProtoFetcher>& info) {
+      return info.param.value() ? "ProtoFetcher" : "JsonFetcher";
+    });
+
+// Tests the filter mode in which all sites are blocked by default.
+class SupervisedUserBlockModeTest : public SupervisedUserURLFilterTestBase {
+ public:
+  void SetUpOnMainThread() override {
+    SupervisedUserURLFilterTestBase::SetUpOnMainThread();
+    Profile* profile = browser()->profile();
+    supervised_user::SupervisedUserSettingsService*
+        supervised_user_settings_service =
+            SupervisedUserSettingsServiceFactory::GetForKey(
+                profile->GetProfileKey());
+    supervised_user_settings_service->SetLocalSetting(
+        supervised_user::kContentPackDefaultFilteringBehavior,
+        base::Value(
+            static_cast<int>(supervised_user::FilteringBehavior::kBlock)));
+  }
+};
+
 // Tests that it's possible to navigate from a blocked page to another blocked
 // page.
 IN_PROC_BROWSER_TEST_F(SupervisedUserBlockModeTest,
@@ -435,126 +562,53 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(SupervisedUserURLFilterTest, GoBackOnDontProceed) {
-  WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  // Ensure navigation completes.
-  EXPECT_TRUE(WaitForLoadStop(web_contents));
-  // We start out at the initial navigation.
-  ASSERT_EQ(0, web_contents->GetController().GetCurrentEntryIndex());
-
+// Navigates to a blocked URL.
+IN_PROC_BROWSER_TEST_F(SupervisedUserBlockModeTest,
+                       SendAccessRequestOnBlockedURL) {
   GURL test_url("http://www.example.com/simple.html");
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
 
-  ASSERT_FALSE(ShownPageIsInterstitial(browser()));
-
-  // Set the host as blocked and wait for the interstitial to appear.
-  base::Value::Dict dict;
-  dict.Set(test_url.host(), false);
-  supervised_user::SupervisedUserSettingsService*
-      supervised_user_settings_service =
-          SupervisedUserSettingsServiceFactory::GetForKey(
-              browser()->profile()->GetProfileKey());
-  supervised_user_settings_service->SetLocalSetting(
-      supervised_user::kContentPackManualBehaviorHosts, std::move(dict));
-
-  supervised_user::SupervisedUserURLFilter* filter =
-      GetSupervisedUserService()->GetURLFilter();
-  ASSERT_EQ(supervised_user::FilteringBehavior::kBlock,
-            filter->GetFilteringBehaviorForURL(test_url));
-
-  content::TestNavigationObserver block_observer(web_contents);
-  block_observer.Wait();
-
-  content::LoadStopObserver observer(web_contents);
-  GoBack(web_contents);
-  observer.Wait();
-
-  // We should have gone back to the initial navigation.
-  EXPECT_EQ(0, web_contents->GetController().GetCurrentEntryIndex());
-}
-
-IN_PROC_BROWSER_TEST_F(SupervisedUserURLFilterTest,
-                       ClosingBlockedTabDoesNotCrash) {
-  WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  // Ensure navigation completes.
-  EXPECT_TRUE(WaitForLoadStop(web_contents));
-  ASSERT_EQ(0, web_contents->GetController().GetCurrentEntryIndex());
-
-  GURL test_url("http://www.example.com/simple.html");
-
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
-  ASSERT_FALSE(ShownPageIsInterstitial(browser()));
-
-  // Set the host as blocked and wait for the interstitial to appear.
-  base::Value::Dict dict;
-  dict.Set(test_url.host(), false);
-  supervised_user::SupervisedUserSettingsService*
-      supervised_user_settings_service =
-          SupervisedUserSettingsServiceFactory::GetForKey(
-              browser()->profile()->GetProfileKey());
-  supervised_user_settings_service->SetLocalSetting(
-      supervised_user::kContentPackManualBehaviorHosts, std::move(dict));
-
-  supervised_user::SupervisedUserURLFilter* filter =
-      GetSupervisedUserService()->GetURLFilter();
-  ASSERT_EQ(supervised_user::FilteringBehavior::kBlock,
-            filter->GetFilteringBehaviorForURL(test_url));
-
-  // Verify that there is no crash when closing the blocked tab
-  // (https://crbug.com/719708).
-  browser()->tab_strip_model()->CloseWebContentsAt(
-      0, TabCloseTypes::CLOSE_USER_GESTURE);
-}
-
-IN_PROC_BROWSER_TEST_F(SupervisedUserURLFilterTest, BlockThenUnblock) {
-  GURL test_url("http://www.example.com/simple.html");
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
-
-  WebContents* web_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-
-  ASSERT_FALSE(ShownPageIsInterstitial(browser()));
-
-  supervised_user::SupervisedUserSettingsService*
-      supervised_user_settings_service =
-          SupervisedUserSettingsServiceFactory::GetForKey(
-              browser()->profile()->GetProfileKey());
-  // Set the host as blocked and wait for the interstitial to appear.
-  {
-    base::Value::Dict dict;
-    dict.Set(test_url.host(), false);
-    supervised_user_settings_service->SetLocalSetting(
-        supervised_user::kContentPackManualBehaviorHosts, std::move(dict));
-  }
-
-  supervised_user::SupervisedUserURLFilter* filter =
-      GetSupervisedUserService()->GetURLFilter();
-  ASSERT_EQ(supervised_user::FilteringBehavior::kBlock,
-            filter->GetFilteringBehaviorForURL(test_url));
-
-  content::TestNavigationObserver block_observer(web_contents);
-  block_observer.Wait();
+  WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents();
 
   ASSERT_TRUE(ShownPageIsInterstitial(browser()));
-  {
-    base::Value::Dict dict;
-    dict.Set(test_url.host(), true);
-    supervised_user_settings_service->SetLocalSetting(
-        supervised_user::kContentPackManualBehaviorHosts, std::move(dict));
-    ASSERT_EQ(supervised_user::FilteringBehavior::kAllow,
-              filter->GetFilteringBehaviorForURL(test_url));
-  }
 
-  content::TestNavigationObserver unblock_observer(web_contents);
-  unblock_observer.Wait();
+  SendAccessRequest(tab);
 
-  ASSERT_EQ(test_url, web_contents->GetLastCommittedURL());
+  // TODO(sergiu): Properly check that the access request was sent here.
+
+  GoBackAndWaitForNavigation(tab);
+
+  // Make sure that the tab is still there.
+  EXPECT_EQ(tab, browser()->tab_strip_model()->GetActiveWebContents());
 
   EXPECT_FALSE(ShownPageIsInterstitial(browser()));
 }
 
+// Navigates to a blocked URL in a new tab. We expect the tab to be closed
+// automatically on pressing the "back" button on the interstitial.
+IN_PROC_BROWSER_TEST_F(SupervisedUserBlockModeTest, OpenBlockedURLInNewTab) {
+  TabStripModel* tab_strip = browser()->tab_strip_model();
+  WebContents* prev_tab = tab_strip->GetActiveWebContents();
+
+  // Open blocked URL in a new tab.
+  GURL test_url("http://www.example.com/simple.html");
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), test_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
+      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
+
+  // Check that we got the interstitial.
+  WebContents* tab = tab_strip->GetActiveWebContents();
+  ASSERT_TRUE(ShownPageIsInterstitial(browser()));
+
+  // On pressing the "back" button, the new tab should be closed, and we should
+  // get back to the previous active tab.
+  TabClosingObserver observer(tab_strip, tab);
+  GoBack(tab);
+  observer.WaitForContentsClosing();
+
+  EXPECT_EQ(prev_tab, tab_strip->GetActiveWebContents());
+}
+
 IN_PROC_BROWSER_TEST_F(SupervisedUserBlockModeTest, Unblock) {
   GURL test_url("http://www.example.com/simple.html");
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
@@ -636,12 +690,19 @@
 };
 
 // Tests that prerendering doesn't check SupervisedUserURLFilter.
-IN_PROC_BROWSER_TEST_F(SupervisedUserURLFilterPrerenderingTest, OnURLChecked) {
+IN_PROC_BROWSER_TEST_P(SupervisedUserURLFilterPrerenderingTest, OnURLChecked) {
   MockSupervisedUserURLFilterObserver observer(
       GetSupervisedUserService()->GetURLFilter());
 
   GURL test_url("http://www.example.com/simple.html");
   EXPECT_CALL(observer, OnURLChecked).Times(1);
+  if (supervised_user::IsProtoApiForClassifyUrlEnabled()) {
+    kids_management_api_mock().AllowSubsequentClassifyUrl();
+    EXPECT_CALL(kids_management_api_mock().classify_url_mock(),
+                ClassifyUrl)
+        .Times(2);  // One for request and one for prerender.
+  }
+
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
   testing::Mock::VerifyAndClearExpectations(&observer);
 
@@ -669,4 +730,13 @@
   EXPECT_CALL(observer, OnURLChecked).Times(1);
   prerender_helper().NavigatePrimaryPage(prerender_url);
 }
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SupervisedUserURLFilterPrerenderingTest,
+    ::testing::Values(UseProtoFetcher(true), UseProtoFetcher(false)),
+    [](const ::testing::TestParamInfo<UseProtoFetcher>& info) {
+      return info.param.value() ? "ProtoFetcher" : "JsonFetcher";
+    });
+
 }  // namespace
diff --git a/chrome/browser/tpcd/heuristics/opener_heuristic_browsertest.cc b/chrome/browser/tpcd/heuristics/opener_heuristic_browsertest.cc
index f0d58af..8985a390 100644
--- a/chrome/browser/tpcd/heuristics/opener_heuristic_browsertest.cc
+++ b/chrome/browser/tpcd/heuristics/opener_heuristic_browsertest.cc
@@ -6,6 +6,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/test/bind.h"
+#include "base/test/gmock_expected_support.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_clock.h"
 #include "base/time/time.h"
@@ -45,6 +46,8 @@
 #include "chrome/browser/ui/browser.h"
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+using base::test::HasValue;
+using base::test::ValueIs;
 using content::NavigationHandle;
 using content::RenderFrameHost;
 using content::WebContents;
@@ -333,7 +336,7 @@
 
   // Note: no previous interaction on a.test.
 
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
 
   std::vector<const ukm::mojom::UkmEntry*> entries =
       ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupPastInteraction");
@@ -347,7 +350,7 @@
 
   RecordInteraction(GURL("https://a.test"), clock_.Now() - base::Hours(3));
 
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.PopupPastInteraction",
@@ -397,7 +400,7 @@
   GURL popup_url = embedded_test_server()->GetURL("b.test", "/title1.html");
   RecordInteraction(popup_url, clock_.Now() - base::Hours(3));
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
 
   // Expect that cookie access was granted for the Popup With Past Interaction
   // heuristic, if the feature is enabled.
@@ -424,7 +427,7 @@
   GURL popup_url = embedded_test_server()->GetURL("c.com", "/title1.html");
   RecordInteraction(popup_url, clock_.Now() - base::Hours(3));
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
-  ASSERT_TRUE(OpenAdTaggedPopup(popup_url).has_value());
+  ASSERT_THAT(OpenAdTaggedPopup(popup_url), HasValue());
 
   // Expect that cookie access was granted for the ad-tagged Popup With Past
   // Interaction heuristic, only if the flag is *off*.
@@ -459,7 +462,7 @@
 
   RecordInteraction(GURL("https://a.test"), clock_.Now() - base::Hours(3));
 
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.PopupPastInteraction",
@@ -490,7 +493,7 @@
 
   RecordInteraction(GURL("https://a.test"), clock_.Now() - base::Hours(3));
 
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.PopupPastInteraction",
@@ -513,8 +516,7 @@
 
   RecordInteraction(GURL("https://a.test"), clock_.Now() - base::Hours(3));
 
-  auto maybe_popup = OpenPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url));
 
   ASSERT_EQ(
       ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupPastInteraction")
@@ -522,7 +524,7 @@
       1u);
 
   ASSERT_TRUE(content::NavigateToURL(
-      *maybe_popup, embedded_test_server()->GetURL("b.test", "/title1.html")));
+      popup, embedded_test_server()->GetURL("b.test", "/title1.html")));
 
   // After another navigation, PopupPastInteraction isn't reported again (i.e.,
   // still once total).
@@ -541,7 +543,7 @@
   // Initialize interaction and popup.
   RecordInteraction(popup_url, clock_.Now() - base::Hours(3));
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
   GetDipsService()->storage()->FlushPostedTasksForTesting();
 
   // Assert that the UKM events and DIPS entries were recorded.
@@ -608,18 +610,17 @@
       embedded_test_server()->GetURL("b.test", "/server-redirect?title1.html");
   GURL final_url = embedded_test_server()->GetURL("b.test", "/title1.html");
 
-  auto maybe_popup = OpenPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url));
 
   clock_.Advance(base::Minutes(1));
-  ASSERT_TRUE(content::NavigateToURL(*maybe_popup, redirect_url, final_url));
+  ASSERT_TRUE(content::NavigateToURL(popup, redirect_url, final_url));
 
   ASSERT_EQ(
       ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupInteraction").size(),
       0u);
 
   clock_.Advance(base::Minutes(1));
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.PopupInteraction",
@@ -671,10 +672,9 @@
   GURL opener_url = embedded_test_server()->GetURL("a.test", "/title1.html");
   GURL popup_url = embedded_test_server()->GetURL("b.test", "/title1.html");
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
-  auto maybe_popup = OpenPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url));
   clock_.Advance(base::Minutes(1));
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   // Expect that cookie access was granted for the Popup With Current
   // Interaction heuristic.
@@ -692,11 +692,10 @@
       embedded_test_server()->GetURL("a.com", "/ad_tagging/frame_factory.html");
   GURL popup_url = embedded_test_server()->GetURL("c.com", "/title1.html");
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
-  auto maybe_popup = OpenAdTaggedPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenAdTaggedPopup(popup_url));
 
   clock_.Advance(base::Minutes(1));
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   // Expect that cookie access was granted for the ad-tagged Popup With Current
   // Interaction heuristic, only if the flag is *off*.
@@ -723,18 +722,17 @@
       embedded_test_server()->GetURL("b.test", "/title1.html");
   GURL final_url = embedded_test_server()->GetURL("c.test", "/title1.html");
 
-  auto maybe_popup = OpenPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url));
 
-  ASSERT_TRUE(content::NavigateToURL(*maybe_popup, interaction_url));
-  SimulateMouseClick(*maybe_popup);
+  ASSERT_TRUE(content::NavigateToURL(popup, interaction_url));
+  SimulateMouseClick(popup);
 
   ASSERT_EQ(
       ukm_recorder.GetEntriesByName("OpenerHeuristic.PopupInteraction").size(),
       1u);
 
-  ASSERT_TRUE(content::NavigateToURL(*maybe_popup, final_url));
-  SimulateMouseClick(*maybe_popup);
+  ASSERT_TRUE(content::NavigateToURL(popup, final_url));
+  SimulateMouseClick(popup);
 
   // The second click was not reported (still only 1 total).
   ASSERT_EQ(
@@ -748,16 +746,15 @@
   GURL popup_url = embedded_test_server()->GetURL("a.test", "/title1.html");
   GURL uncommitted_url = embedded_test_server()->GetURL("c.test", "/nocontent");
 
-  auto maybe_popup = OpenPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url));
 
   clock_.Advance(base::Minutes(1));
   // Attempt a navigation which won't commit (because the HTTP response is No
   // Content).
-  ASSERT_TRUE(content::NavigateToURL(*maybe_popup, uncommitted_url, popup_url));
+  ASSERT_TRUE(content::NavigateToURL(popup, uncommitted_url, popup_url));
 
   clock_.Advance(base::Minutes(1));
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.PopupInteraction",
@@ -795,14 +792,13 @@
 
   // Initialize popup and interaction.
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
-  auto maybe_popup = OpenPopup(popup_url_1);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url_1));
 
   clock_.Advance(base::Minutes(1));
-  ASSERT_TRUE(content::NavigateToURL(*maybe_popup, popup_url_2, popup_url_3));
+  ASSERT_TRUE(content::NavigateToURL(popup, popup_url_2, popup_url_3));
 
   clock_.Advance(base::Minutes(1));
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
   GetDipsService()->storage()->FlushPostedTasksForTesting();
 
   // Assert that the UKM events and DIPS entries were recorded.
@@ -871,7 +867,7 @@
   RecordInteraction(GURL("https://b.test"), clock_.Now() - base::Hours(3));
 
   ASSERT_TRUE(content::NavigateToURL(web_contents, toplevel_url));
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.TopLevel",
@@ -885,10 +881,9 @@
             static_cast<int32_t>(OptionalBool::kFalse));
   EXPECT_EQ(entries[0].metrics["IsAdTaggedPopupClick"], false);
 
-  auto opener_has_iframe = GetOpenerHasSameSiteIframe(
-      ukm_recorder, "OpenerHeuristic.PopupPastInteraction");
-  ASSERT_TRUE(opener_has_iframe.has_value()) << opener_has_iframe.error();
-  EXPECT_EQ(opener_has_iframe.value(), OptionalBool::kFalse);
+  ASSERT_THAT(GetOpenerHasSameSiteIframe(
+                  ukm_recorder, "OpenerHeuristic.PopupPastInteraction"),
+              ValueIs(OptionalBool::kFalse));
 }
 
 IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest,
@@ -900,13 +895,12 @@
 
   ASSERT_TRUE(content::NavigateToURL(web_contents, toplevel_url));
 
-  auto maybe_popup = OpenPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url));
 
   ASSERT_EQ(ukm_recorder.GetEntriesByName("OpenerHeuristic.TopLevel").size(),
             0u);
 
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.TopLevel",
@@ -920,10 +914,9 @@
             static_cast<int32_t>(OptionalBool::kFalse));
   EXPECT_EQ(entries[0].metrics["IsAdTaggedPopupClick"], false);
 
-  auto opener_has_iframe = GetOpenerHasSameSiteIframe(
-      ukm_recorder, "OpenerHeuristic.PopupInteraction");
-  ASSERT_TRUE(opener_has_iframe.has_value()) << opener_has_iframe.error();
-  EXPECT_EQ(opener_has_iframe.value(), OptionalBool::kFalse);
+  ASSERT_THAT(GetOpenerHasSameSiteIframe(ukm_recorder,
+                                         "OpenerHeuristic.PopupInteraction"),
+              ValueIs(OptionalBool::kFalse));
 }
 
 IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest,
@@ -942,7 +935,7 @@
   ASSERT_TRUE(content::NavigateToURL(web_contents, toplevel_url));
   ASSERT_TRUE(content::NavigateIframeToURL(GetActiveWebContents(), iframe_id,
                                            iframe_url));
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.TopLevel",
@@ -955,10 +948,9 @@
   EXPECT_EQ(entries[0].metrics["HasSameSiteIframe"],
             static_cast<int32_t>(OptionalBool::kTrue));
 
-  auto opener_has_iframe = GetOpenerHasSameSiteIframe(
-      ukm_recorder, "OpenerHeuristic.PopupPastInteraction");
-  ASSERT_TRUE(opener_has_iframe.has_value()) << opener_has_iframe.error();
-  EXPECT_EQ(opener_has_iframe.value(), OptionalBool::kTrue);
+  ASSERT_THAT(GetOpenerHasSameSiteIframe(
+                  ukm_recorder, "OpenerHeuristic.PopupPastInteraction"),
+              ValueIs(OptionalBool::kTrue));
 }
 
 IN_PROC_BROWSER_TEST_F(
@@ -971,15 +963,14 @@
 
   ASSERT_TRUE(content::NavigateToURL(web_contents, toplevel_url));
 
-  auto maybe_popup = OpenPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url));
 
   DestroyWebContents(web_contents);
 
   ASSERT_EQ(ukm_recorder.GetEntriesByName("OpenerHeuristic.TopLevel").size(),
             0u);
 
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.TopLevel",
@@ -992,10 +983,9 @@
   EXPECT_EQ(entries[0].metrics["HasSameSiteIframe"],
             static_cast<int32_t>(OptionalBool::kUnknown));
 
-  auto opener_has_iframe = GetOpenerHasSameSiteIframe(
-      ukm_recorder, "OpenerHeuristic.PopupInteraction");
-  ASSERT_TRUE(opener_has_iframe.has_value()) << opener_has_iframe.error();
-  EXPECT_EQ(opener_has_iframe.value(), OptionalBool::kUnknown);
+  ASSERT_THAT(GetOpenerHasSameSiteIframe(ukm_recorder,
+                                         "OpenerHeuristic.PopupInteraction"),
+              ValueIs(OptionalBool::kUnknown));
 }
 
 IN_PROC_BROWSER_TEST_F(
@@ -1010,15 +1000,14 @@
 
   ASSERT_TRUE(content::NavigateToURL(web_contents, toplevel_url));
 
-  auto maybe_popup = OpenPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url));
 
   ASSERT_TRUE(content::NavigateToURL(web_contents, other_url));
 
   ASSERT_EQ(ukm_recorder.GetEntriesByName("OpenerHeuristic.TopLevel").size(),
             0u);
 
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.TopLevel",
@@ -1031,10 +1020,9 @@
   EXPECT_EQ(entries[0].metrics["HasSameSiteIframe"],
             static_cast<int32_t>(OptionalBool::kUnknown));
 
-  auto opener_has_iframe = GetOpenerHasSameSiteIframe(
-      ukm_recorder, "OpenerHeuristic.PopupInteraction");
-  ASSERT_TRUE(opener_has_iframe.has_value()) << opener_has_iframe.error();
-  EXPECT_EQ(opener_has_iframe.value(), OptionalBool::kUnknown);
+  ASSERT_THAT(GetOpenerHasSameSiteIframe(ukm_recorder,
+                                         "OpenerHeuristic.PopupInteraction"),
+              ValueIs(OptionalBool::kUnknown));
 }
 
 IN_PROC_BROWSER_TEST_F(OpenerHeuristicBrowserTest, TopLevel_PopupProvider) {
@@ -1046,7 +1034,7 @@
   RecordInteraction(GURL("https://google.com"), clock_.Now() - base::Hours(3));
 
   ASSERT_TRUE(content::NavigateToURL(web_contents, toplevel_url));
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", {"PopupProvider"});
@@ -1066,10 +1054,9 @@
   RecordInteraction(GURL("https://google.com"), clock_.Now() - base::Hours(3));
 
   ASSERT_TRUE(content::NavigateToURL(web_contents, toplevel_url));
-  auto maybe_popup = OpenPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url));
 
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   // Verify all three events share the same popup id.
   auto tl_entries =
@@ -1095,7 +1082,7 @@
   EXPECT_EQ(ppi_entries[0].metrics["PopupId"], popup_id);
 
   // Open second popup, verify different popup id.
-  ASSERT_TRUE(OpenPopup(popup_url).has_value());
+  ASSERT_THAT(OpenPopup(popup_url), HasValue());
   tl_entries = ukm_recorder.GetEntries("OpenerHeuristic.TopLevel", {"PopupId"});
   ASSERT_EQ(tl_entries.size(), 2u);
   const int64_t popup_id2 = tl_entries[1].metrics["PopupId"];
@@ -1113,7 +1100,7 @@
   RecordInteraction(GURL("https://b.com"), clock_.Now() - base::Hours(3));
 
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), toplevel_url));
-  ASSERT_TRUE(OpenAdTaggedPopup(popup_url).has_value());
+  ASSERT_THAT(OpenAdTaggedPopup(popup_url), HasValue());
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.TopLevel",
@@ -1134,9 +1121,8 @@
   GURL popup_url = embedded_test_server()->GetURL("b.com", "/title1.html");
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), toplevel_url));
 
-  auto maybe_popup = OpenAdTaggedPopup(popup_url);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
-  SimulateMouseClick(*maybe_popup);
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenAdTaggedPopup(popup_url))
+  SimulateMouseClick(popup);
 
   std::vector<ukm::TestAutoSetUkmRecorder::HumanReadableUkmEntry> entries =
       ukm_recorder.GetEntries("OpenerHeuristic.TopLevel",
@@ -1186,24 +1172,22 @@
   // popup_url_1 was opened further back than the backfill lookback period of 10
   // minutes.
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
-  auto maybe_popup = OpenPopup(popup_url_1);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(WebContents * popup, OpenPopup(popup_url_1));
   clock_.Advance(base::Minutes(1));
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   clock_.Advance(base::Minutes(10));
 
   // popup_url_2 was opened with a past interaction, not a current interaction.
   RecordInteraction(popup_url_2, clock_.Now() - base::Hours(3));
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
-  ASSERT_TRUE(OpenPopup(popup_url_2).has_value());
+  ASSERT_THAT(OpenPopup(popup_url_2), HasValue());
 
   // Only popup_url_3 is eligible for a backfill grant.
   ASSERT_TRUE(content::NavigateToURL(GetActiveWebContents(), opener_url));
-  maybe_popup = OpenPopup(popup_url_3);
-  ASSERT_TRUE(maybe_popup.has_value()) << maybe_popup.error();
+  ASSERT_OK_AND_ASSIGN(popup, OpenPopup(popup_url_3));
   clock_.Advance(base::Minutes(1));
-  SimulateMouseClick(*maybe_popup);
+  SimulateMouseClick(popup);
 
   // The pref is updated when the user in onboarded to 3PCD tracking protection,
   // and a PrefChangeRegistrar updates the TrackingProtectionSettingsObservers.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index cc32f93..4d3d438 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3161,6 +3161,8 @@
       "webui/ash/login/os_trial_screen_handler.h",
       "webui/ash/login/osauth/apply_online_password_screen_handler.cc",
       "webui/ash/login/osauth/apply_online_password_screen_handler.h",
+      "webui/ash/login/osauth/factor_setup_success_screen_handler.cc",
+      "webui/ash/login/osauth/factor_setup_success_screen_handler.h",
       "webui/ash/login/osauth/osauth_error_screen_handler.cc",
       "webui/ash/login/osauth/osauth_error_screen_handler.h",
       "webui/ash/login/packaged_license_screen_handler.cc",
diff --git a/chrome/browser/ui/android/passwords/password_generation_editing_popup_view_android.cc b/chrome/browser/ui/android/passwords/password_generation_editing_popup_view_android.cc
index 5d781cefc..0d469ca 100644
--- a/chrome/browser/ui/android/passwords/password_generation_editing_popup_view_android.cc
+++ b/chrome/browser/ui/android/passwords/password_generation_editing_popup_view_android.cc
@@ -97,6 +97,9 @@
 
 void PasswordGenerationEditingPopupViewAndroid::PasswordSelectionUpdated() {}
 
+void PasswordGenerationEditingPopupViewAndroid::EditPasswordSelectionUpdated() {
+}
+
 // static
 PasswordGenerationPopupView* PasswordGenerationPopupView::Create(
     base::WeakPtr<PasswordGenerationPopupController> controller) {
diff --git a/chrome/browser/ui/android/passwords/password_generation_editing_popup_view_android.h b/chrome/browser/ui/android/passwords/password_generation_editing_popup_view_android.h
index f0dea65..d26df30 100644
--- a/chrome/browser/ui/android/passwords/password_generation_editing_popup_view_android.h
+++ b/chrome/browser/ui/android/passwords/password_generation_editing_popup_view_android.h
@@ -43,6 +43,7 @@
   void UpdateState() override;
   bool UpdateBoundsAndRedrawPopup() override;
   void PasswordSelectionUpdated() override;
+  void EditPasswordSelectionUpdated() override;
 
   // Weak pointer to the controller.
   base::WeakPtr<PasswordGenerationPopupController> controller_;
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller.h b/chrome/browser/ui/passwords/password_generation_popup_controller.h
index 47f626ee..01efabd 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller.h
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller.h
@@ -48,6 +48,7 @@
   // Accessors
   virtual GenerationUIState state() const = 0;
   virtual bool password_selected() const = 0;
+  virtual bool edit_password_selected() const = 0;
   virtual const std::u16string& password() const = 0;
 
   // Translated strings
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
index 237d835..14dd183 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
@@ -137,7 +137,6 @@
       controller_common_(bounds,
                          ui_data.text_direction,
                          web_contents->GetNativeView()),
-      password_selected_(false),
       state_(kOfferGeneration),
       key_press_handler_manager_(new KeyPressRegistrator(frame)) {
 #if !BUILDFLAG(IS_ANDROID)
@@ -166,10 +165,23 @@
 
 bool PasswordGenerationPopupControllerImpl::HandleKeyPressEvent(
     const content::NativeWebKeyboardEvent& event) {
+  bool edit_password_enabled = false;
+  // Password generation experiments are defined for Desktop only.
+#if !BUILDFLAG(IS_ANDROID)
+  edit_password_enabled =
+      password_manager::features::kPasswordGenerationExperimentVariationParam
+          .Get() == PasswordGenerationVariation::kEditPassword;
+#endif  // !BUILDFLAG(IS_ANDROID)
+
   switch (event.windows_key_code) {
     case ui::VKEY_UP:
     case ui::VKEY_DOWN:
-      PasswordSelected(true);
+      if (edit_password_enabled && password_selected()) {
+        SelectElement(PasswordGenerationPopupElement::kEditPassword);
+        return true;
+      }
+
+      SelectElement(PasswordGenerationPopupElement::kUseStrongPassword);
       return true;
     case ui::VKEY_ESCAPE:
       HideImpl();
@@ -178,7 +190,7 @@
     case ui::VKEY_TAB:
       // We suppress tab if the password is selected because we will
       // automatically advance focus anyway.
-      return PossiblyAcceptPassword();
+      return PossiblyAcceptSelectedElement();
     default:
       return false;
   }
@@ -188,26 +200,32 @@
   return view_;
 }
 
-bool PasswordGenerationPopupControllerImpl::PossiblyAcceptPassword() {
-  if (password_selected_) {
-    PasswordAccepted();  // This will delete |this|.
-    return true;
+bool PasswordGenerationPopupControllerImpl::PossiblyAcceptSelectedElement() {
+  switch (selected_element_) {
+    case PasswordGenerationPopupElement::kUseStrongPassword:
+      PasswordAccepted();
+      return true;
+    case PasswordGenerationPopupElement::kEditPassword:
+      EditPasswordClicked();
+      return true;
+    default:
+      return false;
   }
-
-  return false;
 }
 
-bool PasswordGenerationPopupControllerImpl::IsPasswordSelectable() const {
+bool PasswordGenerationPopupControllerImpl::IsSelectable() const {
   return state_ == kOfferGeneration;
 }
 
-void PasswordGenerationPopupControllerImpl::PasswordSelected(bool selected) {
-  if (!IsPasswordSelectable() || selected == password_selected_) {
+void PasswordGenerationPopupControllerImpl::SelectElement(
+    PasswordGenerationPopupElement element) {
+  if (!IsSelectable() || selected_element_ == element) {
     return;
   }
 
-  password_selected_ = selected;
+  selected_element_ = element;
   view_->PasswordSelectionUpdated();
+  view_->EditPasswordSelectionUpdated();
 }
 
 void PasswordGenerationPopupControllerImpl::PasswordAccepted() {
@@ -319,15 +337,15 @@
 }
 
 void PasswordGenerationPopupControllerImpl::SelectionCleared() {
-  PasswordSelected(false);
+  SelectElement(PasswordGenerationPopupElement::kNone);
   driver_->ClearPreviewedForm();
 }
 
 void PasswordGenerationPopupControllerImpl::SetSelected() {
-  if (!IsPasswordSelectable()) {
+  if (!IsSelectable()) {
     return;
   }
-  PasswordSelected(true);
+  SelectElement(PasswordGenerationPopupElement::kUseStrongPassword);
   driver_->PreviewGenerationSuggestion(current_generated_password_);
 }
 
@@ -338,6 +356,8 @@
 }
 
 void PasswordGenerationPopupControllerImpl::EditPasswordHovered(bool hovered) {
+  SelectElement(hovered ? PasswordGenerationPopupElement::kEditPassword
+                        : PasswordGenerationPopupElement::kNone);
   if (hovered) {
     driver_->PreviewGenerationSuggestion(current_generated_password_);
   } else {
@@ -408,7 +428,12 @@
 }
 
 bool PasswordGenerationPopupControllerImpl::password_selected() const {
-  return password_selected_;
+  return selected_element_ ==
+         PasswordGenerationPopupElement::kUseStrongPassword;
+}
+
+bool PasswordGenerationPopupControllerImpl::edit_password_selected() const {
+  return selected_element_ == PasswordGenerationPopupElement::kEditPassword;
 }
 
 const std::u16string& PasswordGenerationPopupControllerImpl::password() const {
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
index c73117c..28918d61 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
@@ -141,6 +141,14 @@
 
  private:
   class KeyPressRegistrator;
+
+  // Defines different elements of the popup that can be selected.
+  enum class PasswordGenerationPopupElement {
+    kNone = 0,
+    kUseStrongPassword = 1,
+    kEditPassword = 2,
+  };
+
   // PasswordGenerationPopupController implementation:
   void Hide(autofill::PopupHidingReason) override;
   void ViewDestroyed() override;
@@ -162,19 +170,19 @@
 
   GenerationUIState state() const override;
   bool password_selected() const override;
+  bool edit_password_selected() const override;
   const std::u16string& password() const override;
   std::u16string SuggestedText() const override;
   const std::u16string& HelpText() const override;
 
   bool HandleKeyPressEvent(const content::NativeWebKeyboardEvent& event);
 
-  // Returns whether the password is selectable. This is true iff the password
-  // has not been accepted yet.
-  bool IsPasswordSelectable() const;
-  // Set if the password is currently selected.
-  void PasswordSelected(bool selected);
-  // Accept password if it's selected.
-  bool PossiblyAcceptPassword();
+  // Whether the elements of popup are selectable (true in generation state).
+  bool IsSelectable() const;
+  // Sets currently selected popup element.
+  void SelectElement(PasswordGenerationPopupElement element);
+  // Accepts currently selected element. No-op if no element is selected.
+  bool PossiblyAcceptSelectedElement();
 
   // Handle to the popup. May be NULL if popup isn't showing.
   raw_ptr<PasswordGenerationPopupView> view_;
@@ -209,8 +217,9 @@
   // be displayed in the user generation dialog.
   std::u16string current_generated_password_;
 
-  // Whether the row with the password is currently selected/highlighted.
-  bool password_selected_ = false;
+  // Currently selected / highlighted element of the popup.
+  PasswordGenerationPopupElement selected_element_ =
+      PasswordGenerationPopupElement::kNone;
 
   // The state of the generation popup.
   GenerationUIState state_;
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl_unittest.cc b/chrome/browser/ui/passwords/password_generation_popup_controller_impl_unittest.cc
index 9ed4d820..6b0f58b2 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl_unittest.cc
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl_unittest.cc
@@ -70,6 +70,7 @@
   MOCK_METHOD(void, UpdateGeneratedPasswordValue, (), (override));
   MOCK_METHOD(bool, UpdateBoundsAndRedrawPopup, (), (override));
   MOCK_METHOD(void, PasswordSelectionUpdated, (), (override));
+  MOCK_METHOD(void, EditPasswordSelectionUpdated, (), (override));
 };
 
 class PasswordGenerationPopupControllerImplTest
diff --git a/chrome/browser/ui/passwords/password_generation_popup_view.h b/chrome/browser/ui/passwords/password_generation_popup_view.h
index 48d4b29..17023fc 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_view.h
+++ b/chrome/browser/ui/passwords/password_generation_popup_view.h
@@ -37,6 +37,9 @@
   // Called when the password selection state has changed.
   virtual void PasswordSelectionUpdated() = 0;
 
+  // Called when the edit password selection state has changed.
+  virtual void EditPasswordSelectionUpdated() = 0;
+
   // Note that PasswordGenerationPopupView owns itself, and will only be deleted
   // when Hide() is called.
   static PasswordGenerationPopupView* Create(
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index 1a173a24..7cf1ac58 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -750,14 +750,14 @@
                                      : profile;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
       if (process_startup == chrome::startup::IsProcessStartup::kYes) {
-        auto* user = ash::BrowserContextHelper::Get()->GetUserByBrowserContext(
-            profile_to_open);
-        if (user &&
-            (ash::floating_workspace_util::IsFloatingWorkspaceV2Enabled())) {
-          // If floating workspace is enabled, it will override full restore.
-          // Floating will trigger its own restore from the user's workspace.
+        if (ash::floating_workspace_util::IsFloatingWorkspaceV2Enabled()) {
           ash::FloatingWorkspaceService::GetForProfile(profile_to_open);
-        } else {
+        }
+        // If floating workspace is enabled and safe mode is off, floating
+        // workspace will handle the app restore from user's workspace copy.
+        // Otherwise if safe mode is on, floating workspace will only emit
+        // notification and then delegate the actual work to full restore.
+        if (!ash::floating_workspace_util::ShouldHandleRestartRestore()) {
           // If FullRestoreService is available for the profile (i.e. the full
           // restore feature is enabled and the profile is a regular user
           // profile), defer the browser launching to FullRestoreService code.
diff --git a/chrome/browser/ui/toolbar/pinned_toolbar_actions_model.cc b/chrome/browser/ui/toolbar/pinned_toolbar_actions_model.cc
index c2fa23b..1e4513c8 100644
--- a/chrome/browser/ui/toolbar/pinned_toolbar_actions_model.cc
+++ b/chrome/browser/ui/toolbar/pinned_toolbar_actions_model.cc
@@ -6,6 +6,7 @@
 
 #include <iterator>
 #include <string>
+#include <vector>
 
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
@@ -24,6 +25,7 @@
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "ui/actions/action_id.h"
 #include "ui/actions/actions.h"
 
@@ -104,49 +106,40 @@
   }
 
   if (target_index < 0 || target_index >= int(pinned_action_ids_.size())) {
-    // Do nothing if the index is out of bounds.
+    // Do nothing if the target index is out of bounds.
     return;
   }
 
-  auto iter = base::ranges::find(pinned_action_ids_, action_id);
-  if (iter == pinned_action_ids_.end()) {
+  auto action_to_move = base::ranges::find(pinned_action_ids_, action_id);
+  if (action_to_move == pinned_action_ids_.end()) {
     // Do nothing if this action is not pinned.
     return;
   }
-
-  int start_index = iter - pinned_action_ids_.begin();
+  // If the target index and starting index are the same, do nothing.
+  int start_index = action_to_move - pinned_action_ids_.begin();
   if (start_index == target_index) {
     return;
   }
 
-  const absl::optional<std::string>& action_id_to_move =
-      actions::ActionIdMap::ActionIdToString(action_id);
-  const absl::optional<std::string>& action_id_of_target =
-      actions::ActionIdMap::ActionIdToString(pinned_action_ids_[target_index]);
+  std::vector<actions::ActionId> updated_pinned_action_ids = pinned_action_ids_;
 
-  // Both ActionIds should have a string equivalent.
-  CHECK(action_id_to_move.has_value());
-  CHECK(action_id_of_target.has_value());
+  auto start_iter = base::ranges::find(updated_pinned_action_ids, action_id);
+  CHECK(start_iter != updated_pinned_action_ids.end());
 
-  base::Value::List updated_pinned_action_ids =
-      pref_service_->GetList(prefs::kPinnedActions).Clone();
-  updated_pinned_action_ids.EraseValue(base::Value(action_id_to_move.value()));
+  auto end_iter = base::ranges::find(updated_pinned_action_ids,
+                                     pinned_action_ids_[target_index]);
+  CHECK(end_iter != updated_pinned_action_ids.end());
 
-  auto prefs_iter = base::ranges::find(updated_pinned_action_ids,
-                                       action_id_of_target.value());
-  CHECK(prefs_iter != updated_pinned_action_ids.end());
-
-  if (target_index == 0) {
-    updated_pinned_action_ids.Insert(prefs_iter,
-                                     base::Value(action_id_to_move.value()));
+  // Rotate |action_id| to be in the target position.
+  bool is_left_to_right_move = target_index > start_index;
+  if (is_left_to_right_move) {
+    std::rotate(start_iter, std::next(start_iter), std::next(end_iter));
   } else {
-    updated_pinned_action_ids.Insert(++prefs_iter,
-                                     base::Value(action_id_to_move.value()));
+    std::rotate(end_iter, start_iter, std::next(start_iter));
   }
 
   // Updating the pref causes `UpdatePinnedActionIds()` to be called.
-  pref_service_->SetList(prefs::kPinnedActions,
-                         std::move(updated_pinned_action_ids));
+  UpdatePref(updated_pinned_action_ids);
 
   // Notify observers the action was moved.
   for (Observer& observer : observers_) {
@@ -155,18 +148,12 @@
 }
 
 void PinnedToolbarActionsModel::PinAction(const actions::ActionId& action_id) {
-  base::Value::List updated_pinned_action_ids =
-      pref_service_->GetList(prefs::kPinnedActions).Clone();
-  const absl::optional<std::string>& id =
-      actions::ActionIdMap::ActionIdToString(action_id);
-  // The ActionId should have a string equivalent.
-  CHECK(id.has_value());
+  std::vector<actions::ActionId> updated_pinned_action_ids = pinned_action_ids_;
 
-  updated_pinned_action_ids.Append(base::Value(id.value()));
+  updated_pinned_action_ids.push_back(action_id);
 
   // Updating the pref causes `UpdatePinnedActionIds()` to be called.
-  pref_service_->SetList(prefs::kPinnedActions,
-                         std::move(updated_pinned_action_ids));
+  UpdatePref(updated_pinned_action_ids);
 
   // Notify observers the action was added.
   for (Observer& observer : observers_) {
@@ -176,17 +163,15 @@
 
 void PinnedToolbarActionsModel::UnpinAction(
     const actions::ActionId& action_id) {
-  base::Value::List updated_pinned_action_ids =
-      pref_service_->GetList(prefs::kPinnedActions).Clone();
-  const absl::optional<std::string>& id =
-      actions::ActionIdMap::ActionIdToString(action_id);
-  // The ActionId should have a string equivalent.
-  CHECK(id.has_value());
-  updated_pinned_action_ids.EraseValue(base::Value(id.value()));
+  std::vector<actions::ActionId> updated_pinned_action_ids = pinned_action_ids_;
+  updated_pinned_action_ids.erase(
+      std::remove_if(
+          updated_pinned_action_ids.begin(), updated_pinned_action_ids.end(),
+          [action_id](const actions::ActionId id) { return id == action_id; }),
+      updated_pinned_action_ids.end());
 
   // Updating the pref causes `UpdatePinnedActionIds()` to be called.
-  pref_service_->SetList(prefs::kPinnedActions,
-                         std::move(updated_pinned_action_ids));
+  UpdatePref(std::move(updated_pinned_action_ids));
 
   // Notify observers the action was removed.
   for (Observer& observer : observers_) {
@@ -282,8 +267,7 @@
   bool is_valid_unpin = Contains(kActionSidePanelShowSearchCompanion) &&
                         !companion_should_be_default_pinned;
 
-  base::Value::List updated_pinned_action_ids =
-      pref_service_->GetList(prefs::kPinnedActions).Clone();
+  std::vector<actions::ActionId> updated_pinned_action_ids = pinned_action_ids_;
   const absl::optional<std::string>& id =
       actions::ActionIdMap::ActionIdToString(
           kActionSidePanelShowSearchCompanion);
@@ -291,17 +275,36 @@
   CHECK(id.has_value());
 
   if (is_valid_pin) {
-    updated_pinned_action_ids.Append(base::Value(id.value()));
+    updated_pinned_action_ids.push_back(kActionSidePanelShowSearchCompanion);
   } else if (is_valid_unpin) {
-    updated_pinned_action_ids.EraseValue(base::Value(id.value()));
+    updated_pinned_action_ids.erase(
+        std::remove_if(updated_pinned_action_ids.begin(),
+                       updated_pinned_action_ids.end(),
+                       [](const actions::ActionId id) {
+                         return id == kActionSidePanelShowSearchCompanion;
+                       }),
+        updated_pinned_action_ids.end());
   }
 
   // Updating the pref causes `UpdatePinnedActionIds()` to be called.
-  pref_service_->SetList(prefs::kPinnedActions,
-                         std::move(updated_pinned_action_ids));
+  UpdatePref(updated_pinned_action_ids);
 }
 
 void PinnedToolbarActionsModel::
     MaybeMigrateSearchCompanionPinnedStateForTesting() {
   MaybeMigrateSearchCompanionPinnedState();
 }
+
+void PinnedToolbarActionsModel::UpdatePref(
+    const std::vector<actions::ActionId>& updated_list) {
+  ScopedListPrefUpdate update(pref_service_, prefs::kPinnedActions);
+  base::Value::List& list_of_values = update.Get();
+  list_of_values.clear();
+  for (auto id : updated_list) {
+    const absl::optional<std::string>& id_string =
+        actions::ActionIdMap::ActionIdToString(id);
+    // The ActionId should have a string equivalent.
+    CHECK(id_string.has_value());
+    list_of_values.Append(id_string.value());
+  }
+}
diff --git a/chrome/browser/ui/toolbar/pinned_toolbar_actions_model.h b/chrome/browser/ui/toolbar/pinned_toolbar_actions_model.h
index abf0186b..4a244ceb 100644
--- a/chrome/browser/ui/toolbar/pinned_toolbar_actions_model.h
+++ b/chrome/browser/ui/toolbar/pinned_toolbar_actions_model.h
@@ -107,6 +107,8 @@
   // of the search companion feature.
   void UpdateSearchCompanionDefaultState();
 
+  void UpdatePref(const std::vector<actions::ActionId>& updated_list);
+
   // Our observers.
   base::ObserverList<Observer>::Unchecked observers_;
 
diff --git a/chrome/browser/ui/views/autofill/popup/popup_base_view.cc b/chrome/browser/ui/views/autofill/popup/popup_base_view.cc
index 09e965d..e74bfc1 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_base_view.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_base_view.cc
@@ -327,7 +327,7 @@
       {"PopupSuggestionView", "PopupPasswordSuggestionView", "PopupFooterView",
        "PopupSeparatorView", "PopupWarningView", "PopupBaseView",
        "PasswordGenerationPopupViewViews::GeneratedPasswordBox",
-       "PopupRowContentView"});
+       "PopupRowContentView", "EditPasswordRow"});
   DCHECK(kDerivedClasses.contains(selected_view.GetClassName()))
       << "If you add a new derived class from AutofillPopupRowView, add it "
          "here and to onSelection(evt) in "
diff --git a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
index 201e8eee..7279bdb 100644
--- a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
+++ b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
@@ -174,16 +174,12 @@
     if (controller_) {
       controller_->EditPasswordHovered(true);
     }
-    SetBackground(views::CreateThemedSolidBackground(
-        ui::kColorDropdownBackgroundSelected));
   }
 
   void OnMouseExited(const ui::MouseEvent& event) override {
     if (controller_) {
       controller_->EditPasswordHovered(false);
     }
-    SetBackground(
-        views::CreateThemedSolidBackground(ui::kColorDropdownBackground));
   }
 
   bool OnMousePressed(const ui::MouseEvent& event) override {
@@ -474,6 +470,7 @@
 
 void PasswordGenerationPopupViewViews::UpdateState() {
   password_view_ = nullptr;
+  edit_password_view_ = nullptr;
   RemoveAllChildViews();
   CreateLayoutAndChildren();
 }
@@ -490,15 +487,14 @@
 }
 
 void PasswordGenerationPopupViewViews::PasswordSelectionUpdated() {
-  CHECK(password_view_);
-
-  if (controller_->password_selected()) {
-    DCHECK(this->password_view_);
-    NotifyAXSelection(*this->password_view_);
+  if (!GetWidget()) {
+    return;
   }
 
-  if (!GetWidget())
-    return;
+  CHECK(password_view_);
+  if (controller_->password_selected()) {
+    NotifyAXSelection(*this->password_view_);
+  }
 
   password_view_->UpdateBackground(controller_->password_selected()
                                        ? ui::kColorDropdownBackgroundSelected
@@ -506,6 +502,23 @@
   SchedulePaint();
 }
 
+void PasswordGenerationPopupViewViews::EditPasswordSelectionUpdated() {
+  if (!GetWidget() || !edit_password_view_) {
+    return;
+  }
+
+  if (controller_->edit_password_selected()) {
+    CHECK(this->edit_password_view_);
+    NotifyAXSelection(*this->edit_password_view_);
+  }
+
+  edit_password_view_->SetBackground(views::CreateThemedSolidBackground(
+      controller_->edit_password_selected()
+          ? ui::kColorDropdownBackgroundSelected
+          : ui::kColorDropdownBackground));
+  SchedulePaint();
+}
+
 void PasswordGenerationPopupViewViews::CreateLayoutAndChildren() {
   SetBackground(
       views::CreateThemedSolidBackground(ui::kColorDropdownBackground));
@@ -562,7 +575,7 @@
     auto edit_password_row = std::make_unique<EditPasswordRow>(controller_);
     edit_password_row->SetBorder(views::CreateEmptyBorder(
         gfx::Insets::VH(kVerticalPadding, kHorizontalMargin)));
-    AddChildView(std::move(edit_password_row));
+    edit_password_view_ = AddChildView(std::move(edit_password_row));
   }
 
   AddChildView(views::Builder<views::Separator>()
diff --git a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.h b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.h
index 544f7bc..98684225 100644
--- a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.h
+++ b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.h
@@ -33,6 +33,7 @@
   void UpdateGeneratedPasswordValue() override;
   [[nodiscard]] bool UpdateBoundsAndRedrawPopup() override;
   void PasswordSelectionUpdated() override;
+  void EditPasswordSelectionUpdated() override;
 
  private:
   class GeneratedPasswordBox;
@@ -48,6 +49,9 @@
   // Sub view that displays the actual generated password.
   raw_ptr<GeneratedPasswordBox> password_view_ = nullptr;
 
+  // Sub view that displays the edit password row.
+  raw_ptr<views::View> edit_password_view_ = nullptr;
+
   // Controller for this view. Weak reference.
   base::WeakPtr<PasswordGenerationPopupController> controller_;
 };
diff --git a/chrome/browser/ui/views/passwords/password_generation_popup_view_views_browsertest.cc b/chrome/browser/ui/views/passwords/password_generation_popup_view_views_browsertest.cc
index 95a9800..a3e0e85 100644
--- a/chrome/browser/ui/views/passwords/password_generation_popup_view_views_browsertest.cc
+++ b/chrome/browser/ui/views/passwords/password_generation_popup_view_views_browsertest.cc
@@ -54,6 +54,7 @@
   MOCK_METHOD(std::u16string, GetPrimaryAccountEmail, (), (override));
   MOCK_METHOD(GenerationUIState, state, (), (const override));
   MOCK_METHOD(bool, password_selected, (), (const override));
+  MOCK_METHOD(bool, edit_password_selected, (), (const override));
   MOCK_METHOD(const std::u16string&, password, (), (const override));
   MOCK_METHOD(const std::u16string&, HelpText, (), (const override));
   MOCK_METHOD(std::u16string, SuggestedText, (), (const override));
diff --git a/chrome/browser/ui/views/performance_controls/high_efficiency_interactive_ui_test.cc b/chrome/browser/ui/views/performance_controls/high_efficiency_interactive_ui_test.cc
index d4df443a..6bbad3a 100644
--- a/chrome/browser/ui/views/performance_controls/high_efficiency_interactive_ui_test.cc
+++ b/chrome/browser/ui/views/performance_controls/high_efficiency_interactive_ui_test.cc
@@ -199,8 +199,11 @@
 
   auto PressKeyboard() {
     return Do(base::BindLambdaForTesting([=]() {
+      // Send multiple key presses to reduce flakiness.
       ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_A, false,
                                                   false, false, false));
+      ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_B, false,
+                                                  false, false, false));
     }));
   }
 
@@ -281,7 +284,7 @@
   input_value_updated.event = kInputValueIsUpated;
   input_value_updated.where = input_text_box;
   input_value_updated.type = StateChange::Type::kExistsAndConditionTrue;
-  input_value_updated.test_function = "(el) => { return el.value === 'a'; }";
+  input_value_updated.test_function = "(el) => { return !!el.value; }";
 
   RunTestSequence(
       InstrumentTab(kFirstTabContents, 0),
diff --git a/chrome/browser/ui/views/profiles/first_run_flow_controller_dice.cc b/chrome/browser/ui/views/profiles/first_run_flow_controller_dice.cc
index b194fca0..6acd135 100644
--- a/chrome/browser/ui/views/profiles/first_run_flow_controller_dice.cc
+++ b/chrome/browser/ui/views/profiles/first_run_flow_controller_dice.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/policy/cloud/user_policy_signin_service.h"
 #include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engine_choice/search_engine_choice_service.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/signin_features.h"
 #include "chrome/browser/ui/views/profiles/profile_management_flow_controller.h"
@@ -517,10 +518,13 @@
   auto search_engine_choice_step_completed =
       base::BindOnce(&FirstRunFlowControllerDice::AdvanceToNextPostIdentityStep,
                      base::Unretained(this));
+  SearchEngineChoiceService* search_engine_choice_service =
+      SearchEngineChoiceServiceFactory::GetForProfile(profile_);
   RegisterStep(
       Step::kSearchEngineChoice,
       ProfileManagementStepController::CreateForSearchEngineChoice(
-          host(), SearchEngineChoiceServiceFactory::GetForProfile(profile_),
+          host(), search_engine_choice_service, host()->GetPickerContents(),
+          SearchEngineChoiceService::EntryPoint::kFirstRunExperience,
           std::move(search_engine_choice_step_completed)));
   post_identity_steps.emplace(
       ProfileManagementFlowController::Step::kSearchEngineChoice);
diff --git a/chrome/browser/ui/views/profiles/profile_management_step_controller.cc b/chrome/browser/ui/views/profiles/profile_management_step_controller.cc
index 8588468..bf8139a 100644
--- a/chrome/browser/ui/views/profiles/profile_management_step_controller.cc
+++ b/chrome/browser/ui/views/profiles/profile_management_step_controller.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/profiles/profile_window.h"
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/search_engine_choice/search_engine_choice_service.h"
+#include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
 #include "chrome/browser/ui/profiles/profile_customization_util.h"
 #include "chrome/browser/ui/views/profiles/profile_management_types.h"
 #include "chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.h"
@@ -291,10 +292,16 @@
   SearchEngineChoiceStepController(
       ProfilePickerWebContentsHost* host,
       SearchEngineChoiceService* search_engine_choice_service,
+      content::WebContents* web_contents,
+      SearchEngineChoiceService::EntryPoint entry_point,
       base::OnceClosure step_completed_callback)
       : ProfileManagementStepController(host),
+        entry_point_(entry_point),
         search_engine_choice_service_(search_engine_choice_service),
-        step_completed_callback_(std::move(step_completed_callback)) {}
+        step_completed_callback_(std::move(step_completed_callback)),
+        web_contents_(web_contents) {
+    CHECK(web_contents_);
+  }
 
   void Show(base::OnceCallback<void(bool success)> step_shown_callback,
             bool reset_state) override {
@@ -303,7 +310,7 @@
     bool should_show_search_engine_choice_step =
         search_engine_choice_service_ &&
         search_engines::IsChoiceScreenFlagEnabled(
-            search_engines::ChoicePromo::kFre);
+            search_engines::ChoicePromo::kAny);
 
     if (!should_show_search_engine_choice_step) {
       if (step_shown_callback) {
@@ -324,12 +331,19 @@
               .Then(std::move(navigation_finished_closure));
     }
 
-    search_engines::RecordChoiceScreenEvent(
+    search_engines::SearchEngineChoiceScreenEvents choice_screen_event =
         search_engines::SearchEngineChoiceScreenEvents::
-            kFreChoiceScreenWasDisplayed);
-    host()->ShowScreenInPickerContents(
-        GURL(chrome::kChromeUISearchEngineChoiceURL),
-        std::move(navigation_finished_closure));
+            kProfileCreationChoiceScreenWasDisplayed;
+    if (entry_point_ ==
+        SearchEngineChoiceService::EntryPoint::kFirstRunExperience) {
+      choice_screen_event = search_engines::SearchEngineChoiceScreenEvents::
+          kFreChoiceScreenWasDisplayed;
+    }
+    search_engines::RecordChoiceScreenEvent(choice_screen_event);
+
+    host()->ShowScreen(web_contents_,
+                       GURL(chrome::kChromeUISearchEngineChoiceURL),
+                       std::move(navigation_finished_closure));
   }
 
   void OnNavigateBackRequested() override {
@@ -339,9 +353,7 @@
 
  private:
   void OnLoadFinished() {
-    auto* search_engine_choice_ui = host()
-                                        ->GetPickerContents()
-                                        ->GetWebUI()
+    auto* search_engine_choice_ui = web_contents_->GetWebUI()
                                         ->GetController()
                                         ->GetAs<SearchEngineChoiceUI>();
     CHECK(search_engine_choice_ui);
@@ -349,14 +361,21 @@
     search_engine_choice_ui->Initialize(
         /*display_dialog_callback=*/base::OnceClosure(),
         /*on_choice_made_callback=*/std::move(step_completed_callback_),
-        SearchEngineChoiceService::EntryPoint::kProfilePicker);
+        entry_point_);
   }
 
-  // May be nullptr.
+  // The entry point from which the search engine choice screen is displayed.
+  // This could be either the FRE or Profile Creation in this case.
+  SearchEngineChoiceService::EntryPoint entry_point_;
+
+  // Can be nullptr.
   raw_ptr<SearchEngineChoiceService> search_engine_choice_service_;
 
   // Callback to be executed when the step is completed.
   base::OnceClosure step_completed_callback_;
+
+  // The web contents in which we want to display the screen.
+  raw_ptr<content::WebContents> web_contents_;
 };
 #endif  // BUILDFLAG(ENABLE_SEARCH_ENGINE_CHOICE)
 
@@ -411,9 +430,12 @@
 ProfileManagementStepController::CreateForSearchEngineChoice(
     ProfilePickerWebContentsHost* host,
     SearchEngineChoiceService* search_engine_choice_service,
+    content::WebContents* web_contents,
+    SearchEngineChoiceService::EntryPoint entry_point,
     base::OnceClosure callback) {
   return std::make_unique<SearchEngineChoiceStepController>(
-      host, search_engine_choice_service, std::move(callback));
+      host, search_engine_choice_service, web_contents, entry_point,
+      std::move(callback));
 }
 #endif
 
diff --git a/chrome/browser/ui/views/profiles/profile_management_step_controller.h b/chrome/browser/ui/views/profiles/profile_management_step_controller.h
index 41f3a49e..7adc542c 100644
--- a/chrome/browser/ui/views/profiles/profile_management_step_controller.h
+++ b/chrome/browser/ui/views/profiles/profile_management_step_controller.h
@@ -7,6 +7,8 @@
 
 #include "base/functional/callback.h"
 #include "base/functional/callback_forward.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engine_choice/search_engine_choice_service.h"
 #include "chrome/browser/ui/views/profiles/profile_management_types.h"
 #include "components/signin/public/base/signin_buildflags.h"
 #include "url/gurl.h"
@@ -15,10 +17,6 @@
 #include "chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider.h"
 #endif
 
-#if BUILDFLAG(ENABLE_SEARCH_ENGINE_CHOICE)
-class SearchEngineChoiceService;
-#endif
-
 class ProfilePickerSignedInFlowController;
 class ProfilePickerWebContentsHost;
 
@@ -69,6 +67,8 @@
   CreateForSearchEngineChoice(
       ProfilePickerWebContentsHost* host,
       SearchEngineChoiceService* search_engine_choice_service,
+      content::WebContents* web_contents,
+      SearchEngineChoiceService::EntryPoint entry_point,
       base::OnceClosure callback);
 #endif
 
diff --git a/chrome/browser/ui/views/profiles/profile_picker_flow_controller.cc b/chrome/browser/ui/views/profiles/profile_picker_flow_controller.cc
index 28d0627..e71e510 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_flow_controller.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_flow_controller.cc
@@ -16,6 +16,8 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profile_metrics.h"
 #include "chrome/browser/profiles/profile_window.h"
+#include "chrome/browser/search_engine_choice/search_engine_choice_service.h"
+#include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
@@ -142,7 +144,8 @@
       const CoreAccountInfo& account_info,
       std::unique_ptr<content::WebContents> contents,
       absl::optional<SkColor> profile_color,
-      base::OnceCallback<void(PostHostClearedCallback)> step_completed_callback)
+      base::OnceCallback<void(PostHostClearedCallback, bool)>
+          step_completed_callback)
       : ProfilePickerSignedInFlowController(host,
                                             profile,
                                             account_info,
@@ -201,6 +204,7 @@
     }
     is_finishing_ = true;
 
+    bool is_continue_callback = !callback->is_null();
     if (callback->is_null()) {
       // No custom callback is specified, we can schedule a profile-related
       // experience to be shown in context of the opened fresh profile.
@@ -211,7 +215,7 @@
     profile_name_resolver_->RunWithProfileName(base::BindOnce(
         &ProfileCreationSignedInFlowController::FinishFlow,
         // Unretained ok: `this` outlives `profile_name_resolver_`.
-        base::Unretained(this), std::move(callback)));
+        base::Unretained(this), std::move(callback), is_continue_callback));
   }
 
  private:
@@ -244,6 +248,7 @@
   }
 
   void FinishFlow(PostHostClearedCallback post_host_cleared_callback,
+                  bool is_continue_callback,
                   std::u16string name_for_signed_in_profile) {
     TRACE_EVENT1("browser", "ProfileCreationSignedInFlowController::FinishFlow",
                  "profile_path", profile()->GetPath().AsUTF8Unsafe());
@@ -260,7 +265,7 @@
         ProfileMetrics::ADD_NEW_PROFILE_PICKER_SIGNED_IN);
 
     std::move(step_completed_callback_)
-        .Run(std::move(post_host_cleared_callback));
+        .Run(std::move(post_host_cleared_callback), is_continue_callback);
   }
 
   // Controls whether the flow still needs to finalize (which includes showing
@@ -268,7 +273,8 @@
   bool is_finishing_ = false;
 
   std::unique_ptr<ProfileNameResolver> profile_name_resolver_;
-  base::OnceCallback<void(PostHostClearedCallback)> step_completed_callback_;
+  base::OnceCallback<void(PostHostClearedCallback, bool)>
+      step_completed_callback_;
 };
 
 class ReauthFlowStepController : public ProfileManagementStepController {
@@ -509,11 +515,18 @@
 }
 
 void ProfilePickerFlowController::HandleIdentityStepsCompleted(
-    PostHostClearedCallback post_host_cleared_callback) {
+    PostHostClearedCallback post_host_cleared_callback,
+    bool is_continue_callback) {
   CHECK(post_host_cleared_callback_->is_null());
   CHECK(!post_host_cleared_callback->is_null());
   post_host_cleared_callback_ = std::move(post_host_cleared_callback);
 
+  if (is_continue_callback) {
+    FinishFlowAndRunInBrowser(created_profile_,
+                              std::move(post_host_cleared_callback_));
+    return;
+  }
+
   SwitchToPostIdentitySteps();
 }
 
@@ -522,6 +535,26 @@
   CHECK(created_profile_);
   base::queue<ProfileManagementFlowController::Step> post_identity_steps;
 
+#if BUILDFLAG(ENABLE_SEARCH_ENGINE_CHOICE)
+  if (weak_signed_in_flow_controller_) {
+    auto search_engine_choice_step_completed = base::BindOnce(
+        &ProfilePickerFlowController::AdvanceToNextPostIdentityStep,
+        base::Unretained(this));
+    // TODO(crbug.com/1501785): Find a way to get the web contents without
+    // relying on the weak ptr.
+    SearchEngineChoiceService* search_engine_choice_service =
+        SearchEngineChoiceServiceFactory::GetForProfile(created_profile_);
+    RegisterStep(Step::kSearchEngineChoice,
+                 ProfileManagementStepController::CreateForSearchEngineChoice(
+                     host(), search_engine_choice_service,
+                     weak_signed_in_flow_controller_->contents(),
+                     SearchEngineChoiceService::EntryPoint::kProfileCreation,
+                     std::move(search_engine_choice_step_completed)));
+    post_identity_steps.emplace(
+        ProfileManagementFlowController::Step::kSearchEngineChoice);
+  }
+#endif
+
   RegisterStep(
       Step::kFinishFlow,
       ProfileManagementStepController::CreateForFinishFlowAndRunInBrowser(
diff --git a/chrome/browser/ui/views/profiles/profile_picker_flow_controller.h b/chrome/browser/ui/views/profiles/profile_picker_flow_controller.h
index 2ff984c..c103136 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_flow_controller.h
+++ b/chrome/browser/ui/views/profiles/profile_picker_flow_controller.h
@@ -71,8 +71,12 @@
       const CoreAccountInfo& account_info,
       std::unique_ptr<content::WebContents> contents) override;
 
+  // When `is_continue_callback` is true, the flow should finishing up
+  // immediately so that `post_host_cleared_callback` can be executed, without
+  // showing other steps.
   void HandleIdentityStepsCompleted(
-      PostHostClearedCallback post_host_cleared_callback);
+      PostHostClearedCallback post_host_cleared_callback,
+      bool is_continue_callback);
 
   const ProfilePicker::EntryPoint entry_point_;
 
diff --git a/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.h b/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.h
index c1f2ceb..3c128a2a 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.h
+++ b/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.h
@@ -95,6 +95,8 @@
   // screen. Returns an empty path if no such screen has been displayed.
   base::FilePath switch_profile_path() const { return switch_profile_path_; }
 
+  content::WebContents* contents() const { return contents_.get(); }
+
  protected:
   // Returns the profile color, taking into account current policies.
   absl::optional<SkColor> GetProfileColor() const;
@@ -105,7 +107,6 @@
 
   ProfilePickerWebContentsHost* host() const { return host_; }
   Profile* profile() const { return profile_; }
-  content::WebContents* contents() const { return contents_.get(); }
   std::unique_ptr<content::WebContents> ReleaseContents();
   const CoreAccountInfo& account_info() const { return account_info_; }
 
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl_unittest.cc b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl_unittest.cc
index c867347..2e6fe226 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl_unittest.cc
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_view_impl_unittest.cc
@@ -18,6 +18,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/ax_node_data.h"
+#include "ui/gfx/vector_icon_types.h"
 #include "ui/views/accessibility/view_accessibility.h"
 
 using ::testing::Truly;
@@ -77,10 +78,12 @@
   return widget->GetFocusManager()->GetFocusedView();
 }
 
+const gfx::VectorIcon kEmptyIcon;
+
 const std::vector<sharing_hub::SharingHubAction> kFirstPartyActions = {
-    {0, u"Feed to Dino", nullptr, "feed-to-dino", 0},
-    {1, u"Reverse Star", nullptr, "reverse-star", 0},
-    {2, u"Pastelify", nullptr, "pastelify", 0},
+    {0, u"Feed to Dino", &kEmptyIcon, "feed-to-dino", 0},
+    {1, u"Reverse Star", &kEmptyIcon, "reverse-star", 0},
+    {2, u"Pastelify", &kEmptyIcon, "pastelify", 0},
 };
 
 }  // namespace
diff --git a/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.cc b/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.cc
index 950943e..c0876fe5 100644
--- a/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.cc
+++ b/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.h"
 
+#include <algorithm>
 #include <memory>
 #include <string>
 #include <type_traits>
@@ -15,13 +16,23 @@
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/side_panel/side_panel_enums.h"
+#include "chrome/browser/ui/toolbar/toolbar_pref_names.h"
+#include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
 #include "chrome/browser/ui/views/frame/browser_actions.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_util.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
 #include "chrome/grit/generated_resources.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/compositor/layer_tree_owner.h"
+#include "ui/gfx/text_constants.h"
+#include "ui/gfx/vector_icon_types.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/button/button_controller.h"
 #include "ui/views/layout/animating_layout_manager.h"
@@ -42,25 +53,22 @@
 // TODO(b/299463180): Add right click context menus with an option for pinning
 // unpinning.
 PinnedToolbarActionsContainer::PinnedActionToolbarButton::
-    PinnedActionToolbarButton(Browser* browser, actions::ActionId action_id)
+    PinnedActionToolbarButton(Browser* browser,
+                              actions::ActionId action_id,
+                              views::DragController* drag_controller)
     : ToolbarButton(
           base::BindRepeating(&PinnedActionToolbarButton::ButtonPressed,
                               base::Unretained(this)),
           nullptr,
           nullptr),
+      browser_(browser),
       action_item_(actions::ActionManager::Get().FindAction(
           action_id,
           BrowserActions::FromBrowser(browser)->root_action_item())) {
   CHECK(action_item_);
-  action_changed_subscription_ = action_item_->AddActionChangedCallback(
-      base::BindRepeating(&PinnedToolbarActionsContainer::
-                              PinnedActionToolbarButton::ActionItemChanged,
-                          base::Unretained(this)));
-  ActionItemChanged();
-  OnPropertyChanged(&action_item_, static_cast<views::PropertyEffects>(
-                                       views::kPropertyEffectsLayout |
-                                       views::kPropertyEffectsPaint));
-
+  ConfigureInkDropForToolbar(this);
+  SetHorizontalAlignment(gfx::ALIGN_CENTER);
+  set_drag_controller(drag_controller);
   GetViewAccessibility().OverrideDescription(
       std::u16string(), ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty);
 
@@ -76,6 +84,16 @@
 
   // Do not flip the icon for RTL languages.
   SetFlipCanvasOnPaintForRTLUI(false);
+
+  action_changed_subscription_ = action_item_->AddActionChangedCallback(
+      base::BindRepeating(&PinnedToolbarActionsContainer::
+                              PinnedActionToolbarButton::ActionItemChanged,
+                          base::Unretained(this)));
+  OnPropertyChanged(&action_item_, static_cast<views::PropertyEffects>(
+                                       views::kPropertyEffectsLayout |
+                                       views::kPropertyEffectsPaint));
+
+  ActionItemChanged();
 }
 
 PinnedToolbarActionsContainer::PinnedActionToolbarButton::
@@ -112,6 +130,37 @@
 }
 
 void PinnedToolbarActionsContainer::PinnedActionToolbarButton::
+    SetIconVisibility(bool visible) {
+  if (action_item_->GetImage().IsVectorIcon()) {
+    const auto empty_icon = gfx::VectorIcon();
+    SetVectorIcon(visible
+                      ? *action_item_->GetImage().GetVectorIcon().vector_icon()
+                      : empty_icon);
+  } else {
+    SetImageModel(views::Button::STATE_NORMAL,
+                  visible ? action_item_->GetImage() : ui::ImageModel());
+  }
+}
+
+gfx::Size PinnedToolbarActionsContainer::PinnedActionToolbarButton::
+    CalculatePreferredSize() const {
+  // This makes sure the buttons are at least the toolbar button sized width.
+  // The preferred size might be smaller when the button's icon is removed
+  // during drag/drop.
+  BrowserView* const browser_view =
+      BrowserView::GetBrowserViewForBrowser(browser_);
+  const gfx::Size toolbar_button_size =
+      browser_view
+          ? browser_view->toolbar_button_provider()->GetToolbarButtonSize()
+          : gfx::Size();
+  const gfx::Size preferred_size = ToolbarButton::CalculatePreferredSize();
+  return std::max(preferred_size, toolbar_button_size,
+                  [](const gfx::Size s1, const gfx::Size s2) {
+                    return s1.width() < s2.width();
+                  });
+}
+
+void PinnedToolbarActionsContainer::PinnedActionToolbarButton::
     ActionItemChanged() {
   auto tooltip_text = action_item_->GetTooltipText().empty()
                           ? action_item_->GetText()
@@ -136,6 +185,23 @@
 END_METADATA
 
 ///////////////////////////////////////////////////////////////////////////////
+// PinnedToolbarActionsContainer::DropInfo:
+
+struct PinnedToolbarActionsContainer::DropInfo {
+  explicit DropInfo(actions::ActionId action_id, size_t index);
+
+  // The id for the action being dragged.
+  actions::ActionId action_id;
+
+  // The (0-indexed) index the action will be dropped.
+  size_t index;
+};
+
+PinnedToolbarActionsContainer::DropInfo::DropInfo(actions::ActionId action_id,
+                                                  size_t index)
+    : action_id(action_id), index(index) {}
+
+///////////////////////////////////////////////////////////////////////////////
 // PinnedToolbarActionsContainer:
 
 PinnedToolbarActionsContainer::PinnedToolbarActionsContainer(
@@ -160,7 +226,7 @@
   GetTargetLayoutManager()
       ->SetFlexAllocationOrder(views::FlexAllocationOrder::kReverse)
       .SetDefault(views::kFlexBehaviorKey,
-                  hide_icon_flex_specification.WithOrder(3));
+                  hide_icon_flex_specification.WithOrder(1));
   GetTargetLayoutManager()->SetCrossAxisAlignment(
       views::LayoutAlignment::kCenter);
 
@@ -225,6 +291,95 @@
   ToolbarIconContainerView::OnThemeChanged();
 }
 
+bool PinnedToolbarActionsContainer::GetDropFormats(
+    int* formats,
+    std::set<ui::ClipboardFormatType>* format_types) {
+  return BrowserActionDragData::GetDropFormats(format_types);
+}
+
+bool PinnedToolbarActionsContainer::AreDropTypesRequired() {
+  return BrowserActionDragData::AreDropTypesRequired();
+}
+
+bool PinnedToolbarActionsContainer::CanDrop(const OSExchangeData& data) {
+  return BrowserActionDragData::CanDrop(data,
+                                        browser_view_->browser()->profile());
+}
+
+void PinnedToolbarActionsContainer::OnDragEntered(
+    const ui::DropTargetEvent& event) {
+  drop_weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+int PinnedToolbarActionsContainer::OnDragUpdated(
+    const ui::DropTargetEvent& event) {
+  BrowserActionDragData data;
+  if (!data.Read(event.data())) {
+    return ui::DragDropTypes::DRAG_NONE;
+  }
+
+  // Check if the action item for the dragged icon is pinned (e.g. an action
+  // item could be unpinned through a sync update while dragging its icon).
+  if (!model_->Contains(*actions::ActionIdMap::StringToActionId(data.id()))) {
+    return ui::DragDropTypes::DRAG_NONE;
+  }
+
+  size_t before_icon = 0;
+  // Figure out where to display the icon during dragging transition.
+
+  // First, since we want to update the dragged action's position from before
+  // an icon to after it when the event passes the midpoint between two icons.
+  // This will convert the event coordinate into the index of the icon we want
+  // to display the dragged action before. We also mirror the event.x() so
+  // that our calculations are consistent with left-to-right.
+  // Note we are not including popped-out icons here, only the pinned actions.
+  const int offset_into_icon_area = GetMirroredXInView(event.x());
+  const size_t before_icon_unclamped = WidthToIconCount(offset_into_icon_area);
+
+  const size_t visible_pinned_icons = pinned_buttons_.size();
+
+  // Because the user can drag outside the container bounds, we need to clamp
+  // to the valid range.
+  before_icon = std::min(before_icon_unclamped, visible_pinned_icons - 1);
+
+  if (!drop_info_.get() || drop_info_->index != before_icon) {
+    drop_info_ = std::make_unique<DropInfo>(
+        *actions::ActionIdMap::StringToActionId(data.id()), before_icon);
+    SetActionButtonIconVisibility(drop_info_->action_id, false);
+    ReorderViews();
+  }
+
+  return ui::DragDropTypes::DRAG_MOVE;
+}
+
+void PinnedToolbarActionsContainer::OnDragExited() {
+  if (!drop_info_) {
+    return;
+  }
+
+  const actions::ActionId dragged_action_id = drop_info_->action_id;
+  drop_info_.reset();
+  DragDropCleanup(dragged_action_id);
+}
+
+views::View::DropCallback PinnedToolbarActionsContainer::GetDropCallback(
+    const ui::DropTargetEvent& event) {
+  BrowserActionDragData data;
+  if (!data.Read(event.data())) {
+    return base::NullCallback();
+  }
+
+  auto action_id = drop_info_->action_id;
+  auto index = drop_info_->index;
+  drop_info_.reset();
+  base::ScopedClosureRunner cleanup(
+      base::BindOnce(&PinnedToolbarActionsContainer::DragDropCleanup,
+                     weak_ptr_factory_.GetWeakPtr(), action_id));
+  return base::BindOnce(&PinnedToolbarActionsContainer::MovePinnedAction,
+                        drop_weak_ptr_factory_.GetWeakPtr(), action_id, index,
+                        std::move(cleanup));
+}
+
 void PinnedToolbarActionsContainer::OnActionAdded(const actions::ActionId& id) {
   const auto iter = base::ranges::find(
       pinned_buttons_, id, [](auto* button) { return button->GetActionId(); });
@@ -234,6 +389,8 @@
   AddPinnedActionButtonFor(id);
   GetSidePanelCoordinator()->UpdateHeaderPinButtonState();
   RecordPinnedActionsCount(model_->pinned_action_ids().size());
+
+  drop_weak_ptr_factory_.InvalidateWeakPtrs();
 }
 
 void PinnedToolbarActionsContainer::OnActionRemoved(
@@ -241,12 +398,58 @@
   RemovePinnedActionButtonFor(id);
   GetSidePanelCoordinator()->UpdateHeaderPinButtonState();
   RecordPinnedActionsCount(model_->pinned_action_ids().size());
+
+  drop_weak_ptr_factory_.InvalidateWeakPtrs();
 }
 
 void PinnedToolbarActionsContainer::OnActionMoved(const actions::ActionId& id,
                                                   int from_index,
                                                   int to_index) {
-  ReorderChildView(GetPinnedButtonFor(id), to_index);
+  ReorderViews();
+
+  drop_weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+void PinnedToolbarActionsContainer::WriteDragDataForView(
+    View* sender,
+    const gfx::Point& press_pt,
+    ui::OSExchangeData* data) {
+  DCHECK(data);
+
+  const auto iter = base::ranges::find(pinned_buttons_, sender);
+  DCHECK(iter != pinned_buttons_.end());
+  auto* button = *iter;
+
+  ui::ImageModel icon =
+      ui::ImageModel::FromImageSkia(button->GetImage(button->GetState()));
+  data->provider().SetDragImage(icon.Rasterize(GetColorProvider()),
+                                press_pt.OffsetFromOrigin());
+
+  // Fill in the remaining info.
+  size_t index = iter - pinned_buttons_.begin();
+  BrowserActionDragData drag_data(
+      *actions::ActionIdMap::ActionIdToString(button->GetActionId()), index);
+  drag_data.Write(browser_view_->GetProfile(), data);
+}
+
+int PinnedToolbarActionsContainer::GetDragOperationsForView(
+    View* sender,
+    const gfx::Point& p) {
+  return browser_view_->GetProfile()->IsOffTheRecord()
+             ? ui::DragDropTypes::DRAG_NONE
+             : ui::DragDropTypes::DRAG_MOVE;
+}
+
+bool PinnedToolbarActionsContainer::CanStartDragForView(
+    View* sender,
+    const gfx::Point& press_pt,
+    const gfx::Point& p) {
+  // We don't allow dragging buttons that aren't pinned, or if
+  // the profile is incognito (to avoid changing state from an incognito
+  // window).
+  const auto iter = base::ranges::find(pinned_buttons_, sender);
+  return iter != pinned_buttons_.end() &&
+         !browser_view_->GetProfile()->IsOffTheRecord();
 }
 
 actions::ActionItem* PinnedToolbarActionsContainer::GetActionItemFor(
@@ -259,8 +462,8 @@
 PinnedToolbarActionsContainer::PinnedActionToolbarButton*
 PinnedToolbarActionsContainer::AddPopOutButtonFor(const actions::ActionId& id) {
   CHECK(GetActionItemFor(id));
-  auto popped_out_button =
-      std::make_unique<PinnedActionToolbarButton>(browser_view_->browser(), id);
+  auto popped_out_button = std::make_unique<PinnedActionToolbarButton>(
+      browser_view_->browser(), id, this);
   auto* button = popped_out_button.get();
   popped_out_buttons_.push_back(AddChildView(std::move(popped_out_button)));
   ReorderViews();
@@ -298,7 +501,7 @@
     popped_out_buttons_.erase(iter);
   } else {
     auto button = std::make_unique<PinnedActionToolbarButton>(
-        browser_view_->browser(), id);
+        browser_view_->browser(), id, this);
     pinned_buttons_.push_back(AddChildView(std::move(button)));
   }
   ReorderViews();
@@ -338,10 +541,20 @@
 
 void PinnedToolbarActionsContainer::ReorderViews() {
   size_t index = 0;
-  // Pinned buttons appear first.
-  for (auto* pinned_button : pinned_buttons_) {
-    ReorderChildView(pinned_button, index);
-    index++;
+  // Pinned buttons appear first. Use the model's ordering of pinned ActionIds
+  // because |pinned_buttons_| ordering is not updated on changes from the model
+  // or from the user dragging to reorder.
+  for (auto id : model_->pinned_action_ids()) {
+    if (auto* button = GetPinnedButtonFor(id)) {
+      ReorderChildView(button, index);
+      index++;
+    }
+  }
+
+  // Add the dragged button in its location if a drag is active.
+  if (drop_info_.get()) {
+    ReorderChildView(GetPinnedButtonFor(drop_info_->action_id),
+                     drop_info_->index);
   }
   // The divider exist and is visible after the pinned buttons if any
   // exist.
@@ -364,5 +577,48 @@
       browser_view_->browser());
 }
 
+void PinnedToolbarActionsContainer::SetActionButtonIconVisibility(
+    actions::ActionId id,
+    bool visible) {
+  auto* button = GetPinnedButtonFor(id);
+  if (!button) {
+    return;
+  }
+  button->SetIconVisibility(visible);
+}
+
+void PinnedToolbarActionsContainer::MovePinnedAction(
+    const actions::ActionId& action_id,
+    size_t index,
+    base::ScopedClosureRunner cleanup,
+    const ui::DropTargetEvent& event,
+    ui::mojom::DragOperation& output_drag_op,
+    std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner) {
+  model_->MovePinnedAction(action_id, index);
+
+  output_drag_op = ui::mojom::DragOperation::kMove;
+  // `cleanup` will run automatically when it goes out of scope to finish
+  // up the drag.
+}
+
+void PinnedToolbarActionsContainer::DragDropCleanup(
+    const actions::ActionId& dragged_action_id) {
+  ReorderViews();
+  GetAnimatingLayoutManager()->PostOrQueueAction(base::BindOnce(
+      &PinnedToolbarActionsContainer::SetActionButtonIconVisibility,
+      weak_ptr_factory_.GetWeakPtr(), dragged_action_id, true));
+}
+
+size_t PinnedToolbarActionsContainer::WidthToIconCount(int x_offset) {
+  const int element_padding = GetLayoutConstant(TOOLBAR_ELEMENT_PADDING);
+  size_t unclamped_count = std::max(
+      (x_offset + element_padding) / (browser_view_->toolbar_button_provider()
+                                          ->GetToolbarButtonSize()
+                                          .width() +
+                                      element_padding),
+      0);
+  return std::min(unclamped_count, pinned_buttons_.size());
+}
+
 BEGIN_METADATA(PinnedToolbarActionsContainer)
 END_METADATA
diff --git a/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.h b/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.h
index 5f0a7ac1..ed385415 100644
--- a/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.h
+++ b/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_TOOLBAR_PINNED_TOOLBAR_ACTIONS_CONTAINER_H_
 
 #include <memory>
+#include <set>
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
@@ -17,6 +18,7 @@
 #include "ui/actions/action_id.h"
 #include "ui/actions/actions.h"
 #include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/drag_controller.h"
 
 class Browser;
 class BrowserView;
@@ -27,7 +29,8 @@
 // TODO(b/299463183): Handle highlighting of pinned/popped-out buttons.
 class PinnedToolbarActionsContainer
     : public ToolbarIconContainerView,
-      public PinnedToolbarActionsModel::Observer {
+      public PinnedToolbarActionsModel::Observer,
+      public views::DragController {
   METADATA_HEADER(PinnedToolbarActionsContainer, ToolbarIconContainerView)
 
  public:
@@ -35,21 +38,27 @@
     METADATA_HEADER(PinnedActionToolbarButton, ToolbarButton)
 
    public:
-    PinnedActionToolbarButton(Browser* browser, actions::ActionId action_id);
+    PinnedActionToolbarButton(Browser* browser,
+                              actions::ActionId action_id,
+                              views::DragController* drag_controller);
     ~PinnedActionToolbarButton() override;
 
     actions::ActionId GetActionId();
 
     void ButtonPressed();
+    void AddHighlight();
+    void ResetHighlight();
+    void SetIconVisibility(bool visible);
 
     bool IsActive();
 
-    void AddHighlight();
-    void ResetHighlight();
+    // Button:
+    gfx::Size CalculatePreferredSize() const override;
 
    private:
     void ActionItemChanged();
 
+    raw_ptr<Browser> browser_;
     raw_ptr<actions::ActionItem> action_item_ = nullptr;
     base::CallbackListSubscription action_changed_subscription_;
     // Used to ensure the button remains highlighted while active.
@@ -67,6 +76,15 @@
   // ToolbarIconContainerView:
   void UpdateAllIcons() override;
   void OnThemeChanged() override;
+  bool GetDropFormats(int* formats,
+                      std::set<ui::ClipboardFormatType>* format_types) override;
+  bool AreDropTypesRequired() override;
+  bool CanDrop(const ui::OSExchangeData& data) override;
+  void OnDragEntered(const ui::DropTargetEvent& event) override;
+  int OnDragUpdated(const ui::DropTargetEvent& event) override;
+  void OnDragExited() override;
+  views::View::DropCallback GetDropCallback(
+      const ui::DropTargetEvent& event) override;
 
   // PinnedToolbarActionsModel::Observer:
   void OnActionAdded(const actions::ActionId& id) override;
@@ -76,10 +94,22 @@
                      int to_index) override;
   void OnActionsChanged() override {}
 
+  // views::DragController:
+  void WriteDragDataForView(View* sender,
+                            const gfx::Point& press_pt,
+                            ui::OSExchangeData* data) override;
+  int GetDragOperationsForView(View* sender, const gfx::Point& p) override;
+  bool CanStartDragForView(View* sender,
+                           const gfx::Point& press_pt,
+                           const gfx::Point& p) override;
+
  private:
   friend class PinnedSidePanelInteractiveTest;
   friend class PinnedToolbarActionsContainerTest;
 
+  // A struct representing the position and action being dragged.
+  struct DropInfo;
+
   actions::ActionItem* GetActionItemFor(const actions::ActionId& id);
   PinnedActionToolbarButton* AddPopOutButtonFor(const actions::ActionId& id);
   void RemovePoppedOutButtonFor(const actions::ActionId& id);
@@ -87,9 +117,28 @@
   void RemovePinnedActionButtonFor(const actions::ActionId& id);
   PinnedActionToolbarButton* GetPinnedButtonFor(const actions::ActionId& id);
   PinnedActionToolbarButton* GetPoppedOutButtonFor(const actions::ActionId& id);
-  void ReorderViews();
   SidePanelCoordinator* GetSidePanelCoordinator();
 
+  // Sorts child views to display them in the correct order.
+  void ReorderViews();
+
+  void SetActionButtonIconVisibility(actions::ActionId id, bool visible);
+
+  // Moves the dragged action `action_id`.
+  void MovePinnedAction(
+      const actions::ActionId& action_id,
+      size_t index,
+      base::ScopedClosureRunner cleanup,
+      const ui::DropTargetEvent& event,
+      ui::mojom::DragOperation& output_drag_op,
+      std::unique_ptr<ui::LayerTreeOwner> drag_image_layer_owner);
+
+  // Performs clean up after dragging.
+  void DragDropCleanup(const actions::ActionId& dragged_action_id);
+
+  // Utility function for going from width to icon counts.
+  size_t WidthToIconCount(int x_offset);
+
   const raw_ptr<BrowserView> browser_view_;
 
   std::vector<PinnedActionToolbarButton*> pinned_buttons_;
@@ -100,6 +149,15 @@
   base::ScopedObservation<PinnedToolbarActionsModel,
                           PinnedToolbarActionsModel::Observer>
       model_observation_{this};
+
+  // The DropInfo for the current drag-and-drop operation, or a null pointer if
+  // there is none.
+  std::unique_ptr<DropInfo> drop_info_;
+
+  base::WeakPtrFactory<PinnedToolbarActionsContainer> weak_ptr_factory_{this};
+
+  base::WeakPtrFactory<PinnedToolbarActionsContainer> drop_weak_ptr_factory_{
+      this};
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_TOOLBAR_PINNED_TOOLBAR_ACTIONS_CONTAINER_H_
diff --git a/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container_unittest.cc b/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container_unittest.cc
index ecd6316..2a89607eb 100644
--- a/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container_unittest.cc
+++ b/chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container_unittest.cc
@@ -18,10 +18,16 @@
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/browser_context.h"
 #include "ui/actions/action_id.h"
 #include "ui/actions/actions.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
+#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
+#include "ui/compositor/layer_tree_owner.h"
 #include "ui/events/base_event_utils.h"
+#include "ui/views/layout/animating_layout_manager_test_util.h"
 
 class PinnedToolbarActionsContainerTest : public TestWithBrowserView {
  public:
@@ -334,3 +340,73 @@
             kPinnedToolbarActionsContainerDividerElementId);
   ASSERT_TRUE(child_views[1]->GetVisible());
 }
+
+TEST_F(PinnedToolbarActionsContainerTest, MovingActionsUpdateOrder) {
+  actions::ActionItem* browser_action_item =
+      BrowserActions::FromBrowser(browser_view()->browser())
+          ->root_action_item();
+  auto cut_action =
+      actions::ActionItem::Builder()
+          .SetText(u"Test Action")
+          .SetTooltipText(u"Test Action")
+          .SetActionId(actions::kActionCut)
+          .SetImage(ui::ImageModel::FromVectorIcon(vector_icons::kDogfoodIcon))
+          .SetVisible(true)
+          .SetEnabled(true)
+          .SetInvokeActionCallback(base::DoNothing())
+          .Build();
+  auto copy_action =
+      actions::ActionItem::Builder()
+          .SetText(u"Test Action")
+          .SetTooltipText(u"Test Action")
+          .SetActionId(actions::kActionCopy)
+          .SetImage(ui::ImageModel::FromVectorIcon(vector_icons::kDogfoodIcon))
+          .SetVisible(true)
+          .SetEnabled(true)
+          .SetInvokeActionCallback(base::DoNothing())
+          .Build();
+
+  browser_action_item->AddChild(std::move(cut_action));
+  browser_action_item->AddChild(std::move(copy_action));
+
+  auto* model = PinnedToolbarActionsModel::Get(profile());
+  auto* container =
+      browser_view()->toolbar()->pinned_toolbar_actions_container();
+  ASSERT_TRUE(model);
+  // Verify there are no pinned buttons.
+  auto toolbar_buttons = GetChildToolbarButtons();
+  ASSERT_EQ(toolbar_buttons.size(), 0u);
+  // Pin both and verify order matches the order they were added.
+  model->UpdatePinnedState(actions::kActionCut, true);
+  model->UpdatePinnedState(actions::kActionCopy, true);
+  toolbar_buttons = GetChildToolbarButtons();
+  ASSERT_EQ(toolbar_buttons.size(), 2u);
+  ASSERT_EQ(toolbar_buttons[0]->GetActionId(), actions::kActionCut);
+  ASSERT_EQ(toolbar_buttons[1]->GetActionId(), actions::kActionCopy);
+  // Drag to reorder the two actions.
+  auto* drag_view = toolbar_buttons[1];
+  EXPECT_TRUE(
+      container->CanStartDragForView(drag_view, gfx::Point(), gfx::Point()));
+  ui::OSExchangeData drag_data;
+  container->WriteDragDataForView(drag_view, gfx::Point(), &drag_data);
+  gfx::Point drag_location = toolbar_buttons[0]->bounds().CenterPoint();
+  ui::DropTargetEvent drop_event(drag_data, gfx::PointF(drag_location),
+                                 gfx::PointF(drag_location),
+                                 ui::DragDropTypes::DRAG_MOVE);
+  container->OnDragUpdated(drop_event);
+  auto drop_cb = container->GetDropCallback(drop_event);
+  ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
+  std::move(drop_cb).Run(drop_event, output_drag_op,
+                         /*drag_image_layer_owner=*/nullptr);
+#if BUILDFLAG(IS_MAC)
+  // TODO(crbug.com/1045212): we avoid using animations on Mac due to the lack
+  // of support in unit tests. Therefore this is a no-op.
+#else
+  views::test::WaitForAnimatingLayoutManager(container);
+#endif
+  // Verify the order gets updated in the ui.
+  toolbar_buttons = GetChildToolbarButtons();
+  ASSERT_EQ(toolbar_buttons.size(), 2u);
+  ASSERT_EQ(toolbar_buttons[0]->GetActionId(), actions::kActionCopy);
+  ASSERT_EQ(toolbar_buttons[1]->GetActionId(), actions::kActionCut);
+}
diff --git a/chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.cc b/chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.cc
index 704196e..3d70968 100644
--- a/chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.cc
+++ b/chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.cc
@@ -6,12 +6,12 @@
 
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
+#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/webauthn/sheet_models.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/services/qrcode_generator/public/cpp/qrcode_generator_service.h"
 #include "chrome/services/qrcode_generator/public/mojom/qrcode_generator.mojom.h"
-#include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -33,6 +33,7 @@
 
 constexpr int kQrCodeMargin = 40;
 constexpr int kQrCodeImageSize = 240;
+constexpr int kSecurityKeyIconSize = 30;
 
 }  // namespace
 
@@ -164,7 +165,7 @@
     label_container->AddRows(1, views::TableLayout::kFixedSize);
     label_container->AddChildView(
         std::make_unique<views::ImageView>(ui::ImageModel::FromVectorIcon(
-            vector_icons::kUsbIcon, ui::kColorIcon, 20)));
+            kUsbSecurityKeyIcon, ui::kColorIcon, kSecurityKeyIconSize)));
     auto* label = label_container->AddChildView(
         std::make_unique<views::Label>(sheet_model->GetSecurityKeyLabel(),
                                        views::style::CONTEXT_DIALOG_BODY_TEXT));
diff --git a/chrome/browser/ui/webui/ash/login/local_password_setup_handler.cc b/chrome/browser/ui/webui/ash/login/local_password_setup_handler.cc
index b6e7f4e..ab818069 100644
--- a/chrome/browser/ui/webui/ash/login/local_password_setup_handler.cc
+++ b/chrome/browser/ui/webui/ash/login/local_password_setup_handler.cc
@@ -31,12 +31,6 @@
                 device_name);
   builder->AddF("localPasswordResetTitle", IDS_LOGIN_LOCAL_PASSWORD_RESET_TITLE,
                 device_name);
-  builder->AddF("localPasswordSetupDoneSubtitle",
-                IDS_LOGIN_LOCAL_PASSWORD_SETUP_DONE_SUBTITLE, device_name);
-  builder->Add("localPasswordSetupDoneTitle",
-               IDS_LOGIN_LOCAL_PASSWORD_SETUP_DONE_TITLE);
-  builder->Add("localPasswordResetDoneTitle",
-               IDS_LOGIN_LOCAL_PASSWORD_RESET_DONE_TITLE);
   builder->Add("passwordInputPlaceholderText",
                IDS_LOGIN_MANUAL_PASSWORD_INPUT_LABEL);
   builder->Add("confirmPasswordInputPlaceholderText",
diff --git a/chrome/browser/ui/webui/ash/login/oobe_ui.cc b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
index cf0ca87a..7044a1d 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
@@ -97,6 +97,7 @@
 #include "chrome/browser/ui/webui/ash/login/os_install_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/os_trial_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/osauth/apply_online_password_screen_handler.h"
+#include "chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/osauth/osauth_error_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/packaged_license_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/parental_handoff_screen_handler.h"
@@ -485,6 +486,7 @@
     AddScreenHandler(std::make_unique<ApplyOnlinePasswordScreenHandler>());
   }
   AddScreenHandler(std::make_unique<OSAuthErrorScreenHandler>());
+  AddScreenHandler(std::make_unique<FactorSetupSuccessScreenHandler>());
 
   AddScreenHandler(std::make_unique<GestureNavigationScreenHandler>());
 
diff --git a/chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.cc b/chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.cc
new file mode 100644
index 0000000..d8982f3
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.cc
@@ -0,0 +1,41 @@
+// 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/webui/ash/login/osauth/factor_setup_success_screen_handler.h"
+
+#include <string>
+#include <utility>
+
+#include "base/values.h"
+#include "chrome/browser/ui/webui/ash/login/base_screen_handler.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/login/localized_values_builder.h"
+#include "ui/chromeos/devicetype_utils.h"
+
+namespace ash {
+
+FactorSetupSuccessScreenHandler::FactorSetupSuccessScreenHandler()
+    : BaseScreenHandler(kScreenId) {}
+
+FactorSetupSuccessScreenHandler::~FactorSetupSuccessScreenHandler() = default;
+
+void FactorSetupSuccessScreenHandler::Show(base::Value::Dict params) {
+  ShowInWebUI(std::move(params));
+}
+
+void FactorSetupSuccessScreenHandler::DeclareLocalizedValues(
+    ::login::LocalizedValuesBuilder* builder) {
+  const std::u16string device_name = ui::GetChromeOSDeviceName();
+
+  builder->Add("factorSuccessTitleLocalPasswordUpdated",
+               IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_UPDATED_TITLE);
+  builder->Add("factorSuccessTitleLocalPasswordSet",
+               IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_SET_TITLE);
+  builder->AddF("factorSuccessSubtitleLocalPassword",
+                IDS_LOGIN_FACTOR_SETUP_SUCCESS_LOCAL_PASSWORD_SUBTITLE,
+                device_name);
+  builder->Add("factorSuccessDoneButton", IDS_LOGIN_GENERIC_DONE_BUTTON);
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.h b/chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.h
new file mode 100644
index 0000000..b619719
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/login/osauth/factor_setup_success_screen_handler.h
@@ -0,0 +1,61 @@
+// 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_WEBUI_ASH_LOGIN_OSAUTH_FACTOR_SETUP_SUCCESS_SCREEN_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_ASH_LOGIN_OSAUTH_FACTOR_SETUP_SUCCESS_SCREEN_HANDLER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/ash/login/oobe_screen.h"
+#include "chrome/browser/ui/webui/ash/login/base_screen_handler.h"
+
+namespace login {
+class LocalizedValuesBuilder;
+}
+
+namespace ash {
+
+// Interface for dependency injection between FactorSetupSuccessScreen and its
+// actual representation. Owned by FactorSetupSuccessScreen.
+class FactorSetupSuccessScreenView
+    : public base::SupportsWeakPtr<FactorSetupSuccessScreenView> {
+ public:
+  inline constexpr static StaticOobeScreenId kScreenId{
+      "factor-setup-success", "FactorSetupSuccessScreen"};
+
+  virtual ~FactorSetupSuccessScreenView() = default;
+
+  // Shows the contents of the screen.
+  virtual void Show(base::Value::Dict params) = 0;
+};
+
+// A class that handles the WebUI hooks in error screen.
+class FactorSetupSuccessScreenHandler : public BaseScreenHandler,
+                                        public FactorSetupSuccessScreenView {
+ public:
+  using TView = FactorSetupSuccessScreenView;
+
+  FactorSetupSuccessScreenHandler();
+
+  FactorSetupSuccessScreenHandler(const FactorSetupSuccessScreenHandler&) =
+      delete;
+  FactorSetupSuccessScreenHandler& operator=(
+      const FactorSetupSuccessScreenHandler&) = delete;
+
+  ~FactorSetupSuccessScreenHandler() override;
+
+ private:
+  // FactorSetupSuccessScreenView:
+  void Show(base::Value::Dict params) override;
+
+  // BaseScreenHandler:
+  void DeclareLocalizedValues(
+      ::login::LocalizedValuesBuilder* builder) override;
+
+  base::WeakPtrFactory<FactorSetupSuccessScreenHandler> weak_ptr_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_UI_WEBUI_ASH_LOGIN_OSAUTH_FACTOR_SETUP_SUCCESS_SCREEN_HANDLER_H_
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.cc b/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.cc
index a07aae80..621c9b6 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.cc
@@ -57,7 +57,8 @@
   display_configuration_observers_.Add(std::move(observer));
 }
 
-void DisplaySettingsProvider::OnDidProcessDisplayChanges() {
+void DisplaySettingsProvider::OnDidProcessDisplayChanges(
+    const DisplayConfigurationChange& configuration_change) {
   for (auto& observer : display_configuration_observers_) {
     observer->OnDisplayConfigurationChanged();
   }
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.h b/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.h
index d5586669..2e89f8e 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.h
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.h
@@ -41,7 +41,8 @@
   void OnTabletModeEventsBlockingChanged() override;
 
   // display::DisplayManagerObserver:
-  void OnDidProcessDisplayChanges() override;
+  void OnDidProcessDisplayChanges(
+      const DisplayConfigurationChange& configuration_change) override;
 
  private:
   mojo::RemoteSet<mojom::TabletModeObserver> tablet_mode_observers_;
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider_unittest.cc b/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider_unittest.cc
index 6c0d43e..ee5b0d6 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider_unittest.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider_unittest.cc
@@ -131,7 +131,7 @@
       fake_observer.receiver.BindNewPipeAndPassRemote());
   base::RunLoop().RunUntilIdle();
 
-  provider_->OnDidProcessDisplayChanges();
+  provider_->OnDidProcessDisplayChanges(/*configuration_change=*/{{}, {}, {}});
   fake_observer.WaitForDisplayConfigurationChanged();
 
   EXPECT_EQ(1u, fake_observer.num_display_configuration_changed_calls());
diff --git a/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc b/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
index 4537bc0..733668d2 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/privacy/privacy_section.cc
@@ -488,6 +488,7 @@
       {"privacyHubPermissionDeniedText", IDS_APP_MANAGEMENT_PERMISSION_DENIED},
       {"noAppCanUseMicText",
        IDS_OS_SETTINGS_PRIVACY_HUB_NO_APP_CAN_USE_MIC_TEXT},
+      {"blockedForAllText", IDS_OS_SETTINGS_PRIVACY_HUB_BLOCKED_FOR_ALL_TEXT},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
diff --git a/chrome/browser/ui/webui/browsing_topics/browsing_topics_internals_page_handler.cc b/chrome/browser/ui/webui/browsing_topics/browsing_topics_internals_page_handler.cc
index 3bdf121..181cf4ac 100644
--- a/chrome/browser/ui/webui/browsing_topics/browsing_topics_internals_page_handler.cc
+++ b/chrome/browser/ui/webui/browsing_topics/browsing_topics_internals_page_handler.cc
@@ -33,7 +33,6 @@
           privacy_sandbox::kOverridePrivacySandboxSettingsLocalTesting),
       base::FeatureList::IsEnabled(
           blink::features::kBrowsingTopicsBypassIPIsPubliclyRoutableCheck),
-      base::FeatureList::IsEnabled(blink::features::kBrowsingTopicsXHR),
       base::FeatureList::IsEnabled(blink::features::kBrowsingTopicsDocumentAPI),
       browsing_topics::CurrentConfigVersion(),
       base::FeatureList::IsEnabled(blink::features::kBrowsingTopicsParameters),
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 833617535..cbbedac 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -128,7 +128,7 @@
     AuthenticatorTransport transport) {
   switch (transport) {
     case AuthenticatorTransport::kUsbHumanInterfaceDevice:
-      return vector_icons::kUsbIcon;
+      return kUsbSecurityKeyIcon;
     case AuthenticatorTransport::kInternal:
       return kLaptopIcon;
     case AuthenticatorTransport::kHybrid:
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
index d1bc957..13a6e65 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
@@ -2960,7 +2960,7 @@
           EXPECT_EQ(win_button_it->icon, kLaptopIcon);
           break;
         case kSk:
-          EXPECT_EQ(win_button_it->icon, vector_icons::kUsbIcon);
+          EXPECT_EQ(win_button_it->icon, kUsbSecurityKeyIcon);
           break;
         case kPhoneOrSk:
         case kPhone:
@@ -3017,7 +3017,7 @@
         EXPECT_EQ(win_button_it->icon, kLaptopIcon);
         break;
       case kSk:
-        EXPECT_EQ(win_button_it->icon, vector_icons::kUsbIcon);
+        EXPECT_EQ(win_button_it->icon, kUsbSecurityKeyIcon);
         break;
       default:
         NOTREACHED();
diff --git a/chrome/browser/win/settings_app_monitor.cc b/chrome/browser/win/settings_app_monitor.cc
index c419f8d..05ab94d 100644
--- a/chrome/browser/win/settings_app_monitor.cc
+++ b/chrome/browser/win/settings_app_monitor.cc
@@ -13,7 +13,6 @@
 #include "base/functional/bind.h"
 #include "base/location.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/strings/pattern.h"
 #include "base/strings/string_util.h"
 #include "base/synchronization/lock.h"
@@ -252,11 +251,12 @@
 
   // Invoke the dialog and record whether it was successful.
   Microsoft::WRL::ComPtr<IUIAutomationInvokePattern> invoke_pattern;
-  bool succeeded = SUCCEEDED(browser_button->GetCachedPatternAs(
-                       UIA_InvokePatternId, IID_PPV_ARGS(&invoke_pattern))) &&
-                   invoke_pattern && SUCCEEDED(invoke_pattern->Invoke());
-
-  UMA_HISTOGRAM_BOOLEAN("DefaultBrowser.Win10ChooserInvoked", succeeded);
+  if (!invoke_pattern) {
+    return;
+  }
+  browser_button->GetCachedPatternAs(UIA_InvokePatternId,
+                                     IID_PPV_ARGS(&invoke_pattern));
+  invoke_pattern->Invoke();
 }
 
 SettingsAppMonitor::SettingsAppMonitor(Delegate* delegate)
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 2cc225c..5a7ccdd 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1699963157-6cce2ca93e3696a412638cf5d37ffb817e061e91.profdata
+chrome-android32-main-1699984792-c01d3a6f6bd1316fc9f266153e0113e23ecfa2e0.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index ef6b05c8..9495e1ed 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1699963157-cb9d225eb6af3338b0e1a38f105d1457accfc4c1.profdata
+chrome-android64-main-1699984792-1e1b57414c16e2aed31c52c8f9e1989aa6653ecf.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 2b1ae65..35a5716 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1699952390-4b488ee162166781ceb37b7f7e39a9fe8003ee39.profdata
+chrome-win32-main-1699973819-151cc7e236df33560ba4546a5f96aa119240f6da.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index d50f33c..dd2e2597 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1699963157-e81f450442f424a08373887a66f9d54aafa50274.profdata
+chrome-win64-main-1699973819-b2a80080c484abf23128ca292c3b67675b3edb87.profdata
diff --git a/chrome/common/compose/type_conversions.cc b/chrome/common/compose/type_conversions.cc
index cf390a14..01d875f 100644
--- a/chrome/common/compose/type_conversions.cc
+++ b/chrome/common/compose/type_conversions.cc
@@ -44,10 +44,16 @@
     case ModelExecutionError::kUnknown:
     case ModelExecutionError::kRequestThrottled:
     case ModelExecutionError::kGenericFailure:
+    case ModelExecutionError::kRetryableError:
       return compose::mojom::ComposeStatus::kTryAgainLater;
     case ModelExecutionError::kInvalidRequest:
       return compose::mojom::ComposeStatus::kNotSuccessful;
     case ModelExecutionError::kPermissionDenied:
       return compose::mojom::ComposeStatus::kPermissionDenied;
+    case ModelExecutionError::kNonRetryableError:
+    case ModelExecutionError::kUnsupportedLanguage:
+    case ModelExecutionError::kFiltered:
+    case ModelExecutionError::kDisabled:
+      return compose::mojom::ComposeStatus::kNotSuccessful;
   }
 }
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json
index 151a8a4..8894c055 100644
--- a/chrome/common/extensions/api/_api_features.json
+++ b/chrome/common/extensions/api/_api_features.json
@@ -275,6 +275,9 @@
     "dependencies": ["permission:contentSettings"],
     "contexts": ["blessed_extension"]
   },
+  "contentSettings.clipboard": {
+    "feature_flag": "ApiContentSettingsClipboard"
+  },
   "contextMenus": {
     "dependencies": ["permission:contextMenus"],
     "contexts": ["blessed_extension"]
diff --git a/chrome/common/extensions/api/content_settings.json b/chrome/common/extensions/api/content_settings.json
index 7c757c6..44dc45c 100644
--- a/chrome/common/extensions/api/content_settings.json
+++ b/chrome/common/extensions/api/content_settings.json
@@ -177,6 +177,11 @@
         "enum": ["allow", "block"]
       },
       {
+        "id": "ClipboardContentSetting",
+        "type": "string",
+        "enum": ["allow", "block", "ask"]
+      },
+      {
         "id": "CookiesContentSetting",
         "type": "string",
         "enum": ["allow", "block", "session_only"]
@@ -331,6 +336,14 @@
           {"$ref":"MicrophoneContentSetting"}
         ]
       },
+      "clipboard": {
+        "$ref": "ContentSetting",
+        "description": "Whether to allow sites to access the clipboard via advanced capabilities of the Async Clipboard API. \"Advanced\" capabilities include anything besides writing built-in formats after a user gesture, i.e. the ability to read, the ability to write custom formats, and the ability to write without a user gesture. One of <br><var>allow</var>: Allow sites to use advanced clipboard capabilities,<br><var>block</var>: Don't allow sites to use advanced clipboard capabilties,<br><var>ask</var>: Ask when a site wants to use advanced clipboard capabilities. <br>Default is <var>ask</var>.<br>The primary URL is the URL of the document which requested clipboard access. The secondary URL is not used.",
+        "value": [
+          "clipboard",
+          {"$ref":"ClipboardContentSetting"}
+        ]
+      },
       "camera": {
         "$ref": "ContentSetting",
         "description": "Whether to allow sites to access the camera. One of <br><var>allow</var>: Allow sites to access the camera,<br><var>block</var>: Don't allow sites to access the camera,<br><var>ask</var>: Ask when a site wants to access the camera. <br>Default is <var>ask</var>.<br>The primary URL is the URL of the document which requested camera access. The secondary URL is not used.<br>NOTE: The 'allow' setting is not valid if both patterns are '<all_urls>'.",
diff --git a/chrome/common/extensions/api/cookies.json b/chrome/common/extensions/api/cookies.json
index dc8388e..a7c94eb 100644
--- a/chrome/common/extensions/api/cookies.json
+++ b/chrome/common/extensions/api/cookies.json
@@ -90,7 +90,7 @@
       {
         "name": "getAll",
         "type": "function",
-        "description": "Retrieves all cookies from a single cookie store that match the given information.  The cookies returned will be sorted, with those with the longest path first.  If multiple cookies have the same path length, those with the earliest creation time will be first. Only retrieves cookies for domains which the extension has host permissions to",
+        "description": "Retrieves all cookies from a single cookie store that match the given information.  The cookies returned will be sorted, with those with the longest path first.  If multiple cookies have the same path length, those with the earliest creation time will be first. Only retrieves cookies for domains which the extension has host permissions to.",
         "parameters": [
           {
             "type": "object",
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
index 6f4b6819..4e8e8d6c 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
@@ -111,8 +111,11 @@
         base::StringPiece(response_.data(), response_.size()),
         base::JSON_PARSE_CHROMIUM_EXTENSIONS |
             base::JSON_ALLOW_TRAILING_COMMAS);
-    if (!result || !result->is_dict()) {
-      LOGFN(ERROR) << "Failed to read json result from server response";
+    if (!result) {
+      LOGFN(ERROR) << "base::JSONReader::Read returned 0";
+      result.reset();
+    } else if (!result->is_dict()) {
+      LOGFN(ERROR) << "json result is not a dictionary";
       result.reset();
     }
 
@@ -427,16 +430,26 @@
   DCHECK(request_result);
 
   std::string request_body;
-  if (!base::JSONWriter::Write(request_dict, &request_body)) {
+  if (!request_dict.empty() &&
+      !base::JSONWriter::Write(request_dict, &request_body)) {
     LOGFN(ERROR) << "base::JSONWriter::Write failed";
     return E_FAIL;
   }
+  if ((request_dict.empty() && !request_body.empty()) ||
+      (!request_dict.empty() && request_body.empty())) {
+    LOGFN(ERROR) << "Mismatch between request dict and body";
+    return E_FAIL;
+  }
 
   for (unsigned int try_count = 0; try_count <= request_retries; ++try_count) {
     HttpServiceRequest* request = HttpServiceRequest::Create(
         request_url, access_token, headers, request_body, request_timeout);
-    if (!request)
+    if (!request) {
+      LOGFN(ERROR)
+          << "Could not create an HttpServiceRequest object. request url: "
+          << request_url.spec() << " request body: " << request_body;
       return E_FAIL;
+    }
 
     auto extracted_param =
         request->WaitForResponseFromHttpService(request_timeout);
@@ -444,6 +457,7 @@
       continue;
 
     *request_result = std::move(extracted_param);
+
     const base::Value::Dict* error_detail =
         (*request_result)->GetDict().FindDict(kErrorKeyInRequestResult);
     if (!error_detail)
@@ -460,6 +474,7 @@
     }
   }
 
+  LOGFN(ERROR) << "Unable to serve http service request";
   return E_FAIL;
 }
 
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher_unittests.cc b/chrome/credential_provider/gaiacp/win_http_url_fetcher_unittests.cc
index ea337a8..1c7355d 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher_unittests.cc
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher_unittests.cc
@@ -124,6 +124,50 @@
   }
 }
 
+TEST_P(GcpWinHttpUrlFetcherTest,
+       BuildRequestAndFetchResultFromHttpServiceEmptyRequestTest) {
+  int num_retries = 0;
+  const int timeout_in_millis = 12000;
+  const std::string header1 = "test-header-2";
+  const std::string header1_value = "test-value-2";
+  const GURL test_url = GURL(
+      "https://test-service.googleapis.com/v1/testEndpointForEmptyRequest");
+  const std::string access_token = "test-access-token";
+
+  // send empty request dictionary
+  base::Value::Dict request;
+
+  base::TimeDelta request_timeout = base::Milliseconds(timeout_in_millis);
+  absl::optional<base::Value> request_result;
+
+  auto expected_result = base::Value::Dict()
+                             .Set("response-str-key", "response-str-value")
+                             .Set("response-int-key", 4321);
+  std::string expected_response;
+  base::JSONWriter::Write(expected_result, &expected_response);
+
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      test_url, FakeWinHttpUrlFetcher::Headers(), expected_response);
+
+  fake_http_url_fetcher_factory()->SetCollectRequestData(true);
+
+  HRESULT hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService(
+      test_url, access_token, {{header1, header1_value}}, request,
+      request_timeout, num_retries, &request_result);
+
+  ASSERT_EQ(S_OK, hr);
+  ASSERT_EQ(expected_result, request_result.value());
+  ASSERT_EQ(1UL, fake_http_url_fetcher_factory()->requests_created());
+
+  for (size_t idx = 0;
+       idx < fake_http_url_fetcher_factory()->requests_created(); ++idx) {
+    FakeWinHttpUrlFetcherFactory::RequestData request_data =
+        fake_http_url_fetcher_factory()->GetRequestData(idx);
+
+    ASSERT_EQ("", request_data.body);
+  }
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          GcpWinHttpUrlFetcherTest,
                          ::testing::Combine(::testing::Values(0, 1, 2, 3),
diff --git a/chrome/renderer/worker_content_settings_client.cc b/chrome/renderer/worker_content_settings_client.cc
index 062fb8b3..1c98d6f 100644
--- a/chrome/renderer/worker_content_settings_client.cc
+++ b/chrome/renderer/worker_content_settings_client.cc
@@ -18,7 +18,7 @@
 
 WorkerContentSettingsClient::WorkerContentSettingsClient(
     content::RenderFrame* render_frame)
-    : render_frame_id_(render_frame->GetRoutingID()) {
+    : frame_token_(render_frame->GetWebFrame()->GetLocalFrameToken()) {
   blink::WebLocalFrame* frame = render_frame->GetWebFrame();
   const blink::WebDocument& document = frame->GetDocument();
   if (document.GetSecurityOrigin().IsOpaque() ||
@@ -52,7 +52,7 @@
       site_for_cookies_(other.site_for_cookies_),
       top_frame_origin_(other.top_frame_origin_),
       allow_running_insecure_content_(other.allow_running_insecure_content_),
-      render_frame_id_(other.render_frame_id_) {
+      frame_token_(other.frame_token_) {
   other.EnsureContentSettingsManager();
   other.content_settings_manager_->Clone(
       pending_content_settings_manager_.InitWithNewPipeAndPassReceiver());
@@ -78,7 +78,7 @@
   EnsureContentSettingsManager();
 
   content_settings_manager_->AllowStorageAccess(
-      render_frame_id_,
+      frame_token_,
       content_settings::ContentSettingsAgentImpl::ConvertToMojoStorageType(
           storage_type),
       document_origin_, site_for_cookies_, top_frame_origin_,
@@ -94,7 +94,7 @@
 
   bool result = false;
   content_settings_manager_->AllowStorageAccess(
-      render_frame_id_,
+      frame_token_,
       content_settings::ContentSettingsAgentImpl::ConvertToMojoStorageType(
           storage_type),
       document_origin_, site_for_cookies_, top_frame_origin_, &result);
@@ -107,7 +107,7 @@
   if (!allow_running_insecure_content_ && !allowed_per_settings) {
     EnsureContentSettingsManager();
     content_settings_manager_->OnContentBlocked(
-        render_frame_id_, ContentSettingsType::MIXEDSCRIPT);
+        frame_token_, ContentSettingsType::MIXEDSCRIPT);
     return false;
   }
 
@@ -137,7 +137,7 @@
   if (!allow) {
     EnsureContentSettingsManager();
     content_settings_manager_->OnContentBlocked(
-        render_frame_id_, ContentSettingsType::JAVASCRIPT);
+        frame_token_, ContentSettingsType::JAVASCRIPT);
     return false;
   }
 
diff --git a/chrome/renderer/worker_content_settings_client.h b/chrome/renderer/worker_content_settings_client.h
index a928943..1a4aa335 100644
--- a/chrome/renderer/worker_content_settings_client.h
+++ b/chrome/renderer/worker_content_settings_client.h
@@ -8,6 +8,7 @@
 #include "components/content_settings/common/content_settings_manager.mojom.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/cookies/site_for_cookies.h"
+#include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/platform/web_content_settings_client.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -52,7 +53,7 @@
   net::SiteForCookies site_for_cookies_;
   url::Origin top_frame_origin_;
   bool allow_running_insecure_content_;
-  const int32_t render_frame_id_;
+  const blink::LocalFrameToken frame_token_;
   std::unique_ptr<RendererContentSettingRules> content_setting_rules_;
 
   // Because instances of this class are created on the parent's thread (i.e,
diff --git a/chrome/test/data/extensions/api_test/content_settings/standard/test.js b/chrome/test/data/extensions/api_test/content_settings/standard/test.js
index 641c942..ce58d29 100644
--- a/chrome/test/data/extensions/api_test/content_settings/standard/test.js
+++ b/chrome/test/data/extensions/api_test/content_settings/standard/test.js
@@ -18,6 +18,7 @@
   'microphone': 'ask',
   'camera': 'ask',
   'automaticDownloads': 'ask',
+  'clipboard': 'ask',
   'autoVerify': 'allow'
 };
 
@@ -34,6 +35,8 @@
   'unsandboxedPlugins': 'block',  // Should be ignored.
   'microphone': 'block',
   'camera': 'block',
+  // Conditionally enabled. See crbug.com/1501857
+  'clipboard': 'block',
   'automaticDownloads': 'block'
 };
 
@@ -81,6 +84,12 @@
 chrome.test.runTests([
   function setDefaultContentSettings() {
     default_content_settings.forEach(function(type, setting) {
+      if (type === 'clipboard' && !cs[type]) {
+        // The "clipboard" API may not be present if the feature is disabled.
+        // TODO(https://crbug.com/1501857): Remove this guard once the feature
+        // is stable and removed.
+        return;
+      }
       cs[type].set({
         'primaryPattern': '<all_urls>',
         'secondaryPattern': '<all_urls>',
@@ -90,6 +99,12 @@
   },
   function setContentSettings() {
     settings.forEach(function(type, setting) {
+      if (type === 'clipboard' && !cs[type]) {
+        // The "clipboard" API may not be present if the feature is disabled.
+        // TODO(https://crbug.com/1501857): Remove this guard once the feature
+        // is stable and removed.
+        return;
+      }
       cs[type].set({
         'primaryPattern': 'http://*.google.com/*',
         'secondaryPattern': 'http://*.google.com/*',
@@ -99,6 +114,12 @@
   },
   function getContentSettings() {
     settings.forEach(function(type, setting) {
+      if (type === 'clipboard' && !cs[type]) {
+        // The "clipboard" API may not be present if the feature is disabled.
+        // TODO(https://crbug.com/1501857): Remove this guard once the feature
+        // is stable and removed.
+        return;
+      }
       setting = deprecatedSettingsExpectations[type] || setting;
       var message = "Setting for " + type + " should be " + setting;
       cs[type].get({
diff --git a/chrome/test/data/webapks/extra-field-too-large.apk b/chrome/test/data/webapks/extra-field-too-large.apk
deleted file mode 100644
index ac0c061a..0000000
--- a/chrome/test/data/webapks/extra-field-too-large.apk
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/webapks/extra-len-too-large.apk b/chrome/test/data/webapks/extra-len-too-large.apk
deleted file mode 100644
index 920fae4..0000000
--- a/chrome/test/data/webapks/extra-len-too-large.apk
+++ /dev/null
Binary files differ
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.ts b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.ts
index fe95bb9..9610c08 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.ts
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/accelerator_view_test.ts
@@ -6,6 +6,7 @@
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
 import {IronIconElement} from '//resources/polymer/v3_0/iron-icon/iron-icon.js';
+import {ShortcutInputElement} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input.js';
 import {ShortcutInputKeyElement} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_input_key.js';
 import {KeyInputState} from 'chrome://resources/ash/common/shortcut_input_ui/shortcut_utils.js';
 import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
@@ -63,10 +64,36 @@
     viewElement = null;
   });
 
-  function getInputKey(selector: string): ShortcutInputKeyElement {
-    const element = viewElement!.shadowRoot!.querySelector(selector);
-    assertTrue(!!element);
-    return element as ShortcutInputKeyElement;
+  function getPendingKeyElement(shortcutInputElement: ShortcutInputElement):
+      ShortcutInputKeyElement {
+    return strictQuery(
+        '#pendingKey', shortcutInputElement!.shadowRoot,
+        ShortcutInputKeyElement);
+  }
+
+  function getCtrlElement(shortcutInputElement: ShortcutInputElement):
+      ShortcutInputKeyElement {
+    return strictQuery(
+        '#ctrlKey', shortcutInputElement!.shadowRoot, ShortcutInputKeyElement);
+  }
+
+  function getShiftElement(shortcutInputElement: ShortcutInputElement):
+      ShortcutInputKeyElement {
+    return strictQuery(
+        '#shiftKey', shortcutInputElement!.shadowRoot, ShortcutInputKeyElement);
+  }
+
+  function getAltElement(shortcutInputElement: ShortcutInputElement):
+      ShortcutInputKeyElement {
+    return strictQuery(
+        '#altKey', shortcutInputElement!.shadowRoot, ShortcutInputKeyElement);
+  }
+
+  function getSearchElement(shortcutInputElement: ShortcutInputElement):
+      ShortcutInputKeyElement {
+    return strictQuery(
+        '#searchKey', shortcutInputElement!.shadowRoot,
+        ShortcutInputKeyElement);
   }
 
   function getLockIcon(): HTMLDivElement {
@@ -109,11 +136,14 @@
 
     await flush();
 
-    let ctrlKey = getInputKey('#ctrlKey');
-    let altKey = getInputKey('#altKey');
-    let shiftKey = getInputKey('#shiftKey');
-    let metaKey = getInputKey('#searchKey');
-    let pendingKey = getInputKey('#pendingKey');
+    const shortcutInput = strictQuery(
+        'shortcut-input', viewElement!.shadowRoot, ShortcutInputElement);
+
+    let ctrlKey = getCtrlElement(shortcutInput);
+    let altKey = getAltElement(shortcutInput);
+    let metaKey = getSearchElement(shortcutInput);
+    let shiftKey = getShiftElement(shortcutInput);
+    let pendingKey = getPendingKeyElement(shortcutInput);
 
     // By default, no keys should be registered.
     assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
@@ -161,11 +191,12 @@
     }));
 
     await flush();
-    ctrlKey = getInputKey('#ctrlKey');
-    altKey = getInputKey('#altKey');
-    shiftKey = getInputKey('#shiftKey');
-    metaKey = getInputKey('#searchKey');
-    pendingKey = getInputKey('#pendingKey');
+
+    ctrlKey = getCtrlElement(shortcutInput);
+    altKey = getAltElement(shortcutInput);
+    metaKey = getSearchElement(shortcutInput);
+    shiftKey = getShiftElement(shortcutInput);
+    pendingKey = getPendingKeyElement(shortcutInput);
 
     assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
     assertEquals(KeyInputState.NOT_SELECTED, altKey.keyState);
@@ -183,7 +214,7 @@
       metaKey: false,
     }));
     await flush();
-    pendingKey = getInputKey('#pendingKey');
+    pendingKey = getPendingKeyElement(shortcutInput);
 
     assertEquals(KeyInputState.ALPHANUMERIC_SELECTED, pendingKey.keyState);
     assertEquals('e', pendingKey.key);
@@ -200,11 +231,12 @@
     }));
 
     await flush();
-    ctrlKey = getInputKey('#ctrlKey');
-    altKey = getInputKey('#altKey');
-    shiftKey = getInputKey('#shiftKey');
-    metaKey = getInputKey('#searchKey');
-    pendingKey = getInputKey('#pendingKey');
+
+    ctrlKey = getCtrlElement(shortcutInput);
+    altKey = getAltElement(shortcutInput);
+    metaKey = getSearchElement(shortcutInput);
+    shiftKey = getShiftElement(shortcutInput);
+    pendingKey = getPendingKeyElement(shortcutInput);
 
     assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
     assertEquals(KeyInputState.NOT_SELECTED, altKey.keyState);
@@ -226,11 +258,14 @@
 
     await flushTasks();
 
-    const ctrlKey = getInputKey('#ctrlKey');
-    const altKey = getInputKey('#altKey');
-    const shiftKey = getInputKey('#shiftKey');
-    const metaKey = getInputKey('#searchKey');
-    const pendingKey = getInputKey('#pendingKey');
+    const shortcutInput = strictQuery(
+        'shortcut-input', viewElement!.shadowRoot, ShortcutInputElement);
+
+    const ctrlKey = getCtrlElement(shortcutInput);
+    const altKey = getAltElement(shortcutInput);
+    const metaKey = getSearchElement(shortcutInput);
+    const shiftKey = getShiftElement(shortcutInput);
+    const pendingKey = getPendingKeyElement(shortcutInput);
 
     // By default, no keys should be registered.
     assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
@@ -424,7 +459,9 @@
     viewElement.viewState = ViewState.EDIT;
     await flush();
 
-    const pendingKey = getInputKey('#pendingKey');
+    const shortcutInput = strictQuery(
+        'shortcut-input', viewElement!.shadowRoot, ShortcutInputElement);
+    const pendingKey = getPendingKeyElement(shortcutInput);
 
     const fakeResult: AcceleratorResultData = {
       result: AcceleratorConfigResult.kConflict,
@@ -582,11 +619,14 @@
     // Assert that this is in the EDIT state.
     assertEquals(ViewState.EDIT, viewElement.viewState);
 
-    let ctrlKey = getInputKey('#ctrlKey');
-    let altKey = getInputKey('#altKey');
-    let shiftKey = getInputKey('#shiftKey');
-    let metaKey = getInputKey('#searchKey');
-    let pendingKey = getInputKey('#pendingKey');
+    const shortcutInput = strictQuery(
+        'shortcut-input', viewElement!.shadowRoot, ShortcutInputElement);
+
+    let ctrlKey = getCtrlElement(shortcutInput);
+    let altKey = getAltElement(shortcutInput);
+    let metaKey = getSearchElement(shortcutInput);
+    let shiftKey = getShiftElement(shortcutInput);
+    let pendingKey = getPendingKeyElement(shortcutInput);
 
     // By default, no keys should be registered.
     assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
@@ -627,11 +667,12 @@
     }));
 
     await flush();
-    ctrlKey = getInputKey('#ctrlKey');
-    altKey = getInputKey('#altKey');
-    shiftKey = getInputKey('#shiftKey');
-    metaKey = getInputKey('#searchKey');
-    pendingKey = getInputKey('#pendingKey');
+
+    ctrlKey = getCtrlElement(shortcutInput);
+    altKey = getAltElement(shortcutInput);
+    metaKey = getSearchElement(shortcutInput);
+    shiftKey = getShiftElement(shortcutInput);
+    pendingKey = getPendingKeyElement(shortcutInput);
 
     assertEquals(KeyInputState.NOT_SELECTED, ctrlKey.keyState);
     assertEquals(KeyInputState.MODIFIER_SELECTED, altKey.keyState);
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index e1f39cf..d7cef64 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -273,6 +273,7 @@
     "os_privacy_page/manage_users_subpage_test.ts",
     "os_privacy_page/os_privacy_page_test.ts",
     "os_privacy_page/privacy_hub_app_permission_row_test.ts",
+    "os_privacy_page/privacy_hub_camera_subpage_test.ts",
     "os_privacy_page/privacy_hub_microphone_subpage_test.ts",
     "os_privacy_page/privacy_hub_subpage_test.ts",
     "os_privacy_page/smart_privacy_subpage_test.ts",
diff --git a/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_camera_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_camera_subpage_test.ts
new file mode 100644
index 0000000..23daede
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_camera_subpage_test.ts
@@ -0,0 +1,239 @@
+// 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.
+
+import 'chrome://os-settings/lazy_load.js';
+
+import {MediaDevicesProxy, PrivacyHubBrowserProxyImpl, SettingsPrivacyHubCameraSubpage} from 'chrome://os-settings/lazy_load.js';
+import {CrToggleElement, Router} from 'chrome://os-settings/os_settings.js';
+import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
+import {DomRepeat, flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals, assertFalse, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
+
+import {FakeMediaDevices} from '../fake_media_devices.js';
+
+import {TestPrivacyHubBrowserProxy} from './test_privacy_hub_browser_proxy.js';
+
+suite('<settings-privacy-hub-camera-subpage>', () => {
+  let privacyHubCameraSubpage: SettingsPrivacyHubCameraSubpage;
+  let privacyHubBrowserProxy: TestPrivacyHubBrowserProxy;
+  let mediaDevices: FakeMediaDevices;
+
+  setup(() => {
+    privacyHubBrowserProxy = new TestPrivacyHubBrowserProxy();
+    PrivacyHubBrowserProxyImpl.setInstanceForTesting(privacyHubBrowserProxy);
+
+    mediaDevices = new FakeMediaDevices();
+    MediaDevicesProxy.setMediaDevicesForTesting(mediaDevices);
+
+    privacyHubCameraSubpage =
+        document.createElement('settings-privacy-hub-camera-subpage');
+    const prefs = {
+      'ash': {
+        'user': {
+          'camera_allowed': {
+            value: true,
+          },
+        },
+      },
+    };
+    privacyHubCameraSubpage.prefs = prefs;
+    document.body.appendChild(privacyHubCameraSubpage);
+    flush();
+  });
+
+  teardown(() => {
+    privacyHubCameraSubpage.remove();
+    Router.getInstance().resetRouteForTesting();
+  });
+
+  function getCameraCrToggle(): CrToggleElement {
+    const crToggle =
+        privacyHubCameraSubpage.shadowRoot!.querySelector<CrToggleElement>(
+            '#cameraToggle');
+    assertTrue(!!crToggle);
+    return crToggle;
+  }
+
+  function getOnOffText(): string {
+    return privacyHubCameraSubpage.shadowRoot!.querySelector('#onOffText')!
+        .textContent!.trim();
+  }
+
+  function getOnOffSubtext(): string {
+    return privacyHubCameraSubpage.shadowRoot!.querySelector('#onOffSubtext')!
+        .textContent!.trim();
+  }
+
+  function isCameraListSectionVisible(): boolean {
+    return isVisible(privacyHubCameraSubpage.shadowRoot!.querySelector(
+        '#cameraListSection'));
+  }
+
+  function getNoCameraTextElement(): HTMLDivElement|null {
+    return privacyHubCameraSubpage.shadowRoot!.querySelector('#noCameraText');
+  }
+
+  function getCameraList(): DomRepeat|null {
+    return privacyHubCameraSubpage.shadowRoot!.querySelector<DomRepeat>(
+        '#cameraList');
+  }
+
+  test('Camera section view when access is enabled', () => {
+    const cameraToggle = getCameraCrToggle();
+
+    assertTrue(cameraToggle.checked);
+    assertEquals(privacyHubCameraSubpage.i18n('deviceOn'), getOnOffText());
+    assertEquals(
+        privacyHubCameraSubpage.i18n('cameraToggleSubtext'), getOnOffSubtext());
+    assertTrue(isCameraListSectionVisible());
+  });
+
+  test('Camera section view when access is disabled', async () => {
+    mediaDevices.addDevice('videoinput', 'Fake Camera');
+    await flushTasks();
+
+    const cameraToggle = getCameraCrToggle();
+
+    // Disable camera access.
+    cameraToggle.click();
+    flush();
+
+    assertFalse(cameraToggle.checked);
+    assertEquals(privacyHubCameraSubpage.i18n('deviceOff'), getOnOffText());
+    assertEquals(
+        privacyHubCameraSubpage.i18n('blockedForAllText'), getOnOffSubtext());
+    assertFalse(isCameraListSectionVisible());
+  });
+
+  test('Repeatedly toggle camera access', async () => {
+    mediaDevices.addDevice('videoinput', 'Fake Camera');
+    await flushTasks();
+
+    const cameraToggle = getCameraCrToggle();
+
+    for (let i = 0; i < 3; i++) {
+      cameraToggle.click();
+      flush();
+
+      assertEquals(
+          cameraToggle.checked,
+          privacyHubCameraSubpage.prefs.ash.user.camera_allowed.value);
+    }
+  });
+
+  test('No camera connected and toggle disabled by default', () => {
+    assertTrue(getCameraCrToggle().disabled);
+    assertNull(getCameraList());
+    assertTrue(!!getNoCameraTextElement());
+    assertEquals(
+        privacyHubCameraSubpage.i18n('noCameraConnectedText'),
+        getNoCameraTextElement()!.textContent!.trim());
+  });
+
+  test('Change force-disable-camera-switch', async () => {
+    mediaDevices.addDevice('videoinput', 'Fake Camera');
+    await flushTasks();
+
+    assertFalse(getCameraCrToggle().disabled);
+
+    webUIListenerCallback('force-disable-camera-switch', true);
+    await flushTasks();
+
+    assertTrue(getCameraCrToggle().disabled);
+
+    webUIListenerCallback('force-disable-camera-switch', false);
+    await flushTasks();
+
+    assertFalse(getCameraCrToggle().disabled);
+  });
+
+  test('Toggle enabled when at least one camera connected', async () => {
+    mediaDevices.addDevice('videoinput', 'Fake Camera');
+    await flushTasks();
+
+    assertFalse(getCameraCrToggle().disabled);
+    assertNull(getNoCameraTextElement());
+  });
+
+  test('Camera list updated when a camera is added or removed', async () => {
+    const testDevices = [
+      {
+        device: {
+          kind: 'audiooutput',
+          label: 'Fake Speaker 1',
+        },
+      },
+      {
+        device: {
+          kind: 'videoinput',
+          label: 'Fake Camera 1',
+        },
+      },
+      {
+        device: {
+          kind: 'audioinput',
+          label: 'Fake Microphone 1',
+        },
+      },
+      {
+        device: {
+          kind: 'videoinput',
+          label: 'Fake Camera 2',
+        },
+      },
+      {
+        device: {
+          kind: 'audiooutput',
+          label: 'Fake Speaker 2',
+        },
+      },
+      {
+        device: {
+          kind: 'audioinput',
+          label: 'Fake Microphone 2',
+        },
+      },
+    ];
+
+    let cameraCount = 0;
+
+    // Adding a media device in each iteration.
+    for (const test of testDevices) {
+      mediaDevices.addDevice(test.device.kind, test.device.label);
+      await flushTasks();
+
+      if (test.device.kind === 'videoinput') {
+        cameraCount++;
+      }
+
+      const cameraList = getCameraList();
+      if (cameraCount) {
+        assertTrue(!!cameraList);
+        assertEquals(cameraCount, cameraList.items!.length);
+      } else {
+        assertNull(cameraList);
+      }
+    }
+
+    // Removing the most recently added media device in each iteration.
+    for (const test of testDevices.reverse()) {
+      mediaDevices.popDevice();
+      await flushTasks();
+
+      if (test.device.kind === 'videoinput') {
+        cameraCount--;
+      }
+
+      const cameraList = getCameraList();
+      if (cameraCount) {
+        assertTrue(!!cameraList);
+        assertEquals(cameraCount, cameraList.items!.length);
+      } else {
+        assertNull(cameraList);
+      }
+    }
+  });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_microphone_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_microphone_subpage_test.ts
index 63bdf55..ee2bdd9 100644
--- a/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_microphone_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_privacy_page/privacy_hub_microphone_subpage_test.ts
@@ -89,7 +89,7 @@
         '#microphoneListSection'));
   }
 
-  function getNoMicrophoneText(): HTMLDivElement|null {
+  function getNoMicrophoneTextElement(): HTMLDivElement|null {
     return privacyHubMicrophoneSubpage.shadowRoot!.querySelector(
         '#noMicrophoneText');
   }
@@ -135,7 +135,9 @@
                 .value);
         assertEquals(
             privacyHubMicrophoneSubpage.i18n('deviceOff'), getOnOffText());
-        assertEquals('Blocked for all', getOnOffSubtext());
+        assertEquals(
+            privacyHubMicrophoneSubpage.i18n('blockedForAllText'),
+            getOnOffSubtext());
         assertFalse(isMicrophoneListSectionVisible());
       });
 
@@ -159,10 +161,10 @@
 
   test('No microphone connected by default', () => {
     assertNull(getMicrophoneList());
-    assertTrue(!!getNoMicrophoneText());
+    assertTrue(!!getNoMicrophoneTextElement());
     assertEquals(
         privacyHubMicrophoneSubpage.i18n('noMicrophoneConnectedText'),
-        getNoMicrophoneText()!.textContent!.trim());
+        getNoMicrophoneTextElement()!.textContent!.trim());
   });
 
   test(
@@ -181,7 +183,7 @@
 
         assertFalse(getMicrophoneCrToggle()!.disabled);
         assertTrue(getMicrophoneTooltip()!.hidden);
-        assertNull(getNoMicrophoneText());
+        assertNull(getNoMicrophoneTextElement());
       });
 
   test(
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index 1b717bdd0..6c10db9 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -902,6 +902,16 @@
    'os_privacy_page/privacy_hub_app_permission_row_test.js'
  ],
  [
+   'OsPrivacyPagePrivacyHubCameraSubpage',
+   'os_privacy_page/privacy_hub_camera_subpage_test.js',
+   {
+     enabled: [
+       'ash::features::kCrosPrivacyHubV0',
+       'ash::features::kCrosPrivacyHubAppPermissions'
+     ]
+   },
+ ],
+ [
    'OsPrivacyPagePrivacyHubMicrophoneSubpage',
    'os_privacy_page/privacy_hub_microphone_subpage_test.js',
    {
diff --git a/chrome/test/fuzzing/BUILD.gn b/chrome/test/fuzzing/BUILD.gn
index c55bb3b..2772391 100644
--- a/chrome/test/fuzzing/BUILD.gn
+++ b/chrome/test/fuzzing/BUILD.gn
@@ -51,6 +51,12 @@
   in_process_proto_fuzzer("page_load_in_process_fuzzer") {
     sources = [ "page_load_in_process_fuzzer.cc" ]
     proto_source = "page_load_in_process_fuzzer.proto"
+    seed_corpus_sources = [
+      "page_load_in_process_fuzzer_seed_corpus/cross_origin.textproto",
+      "page_load_in_process_fuzzer_seed_corpus/network.textproto",
+      "page_load_in_process_fuzzer_seed_corpus/simple.textproto",
+    ]
+    testcase_proto_kind = "test.fuzzing.page_load_fuzzing.FuzzCase"
   }
   in_process_proto_fuzzer("kombucha_in_process_fuzzer") {
     sources = [
diff --git a/chrome/test/fuzzing/in_process_proto_fuzzer.h b/chrome/test/fuzzing/in_process_proto_fuzzer.h
index eecf6e7e..dfe7b51 100644
--- a/chrome/test/fuzzing/in_process_proto_fuzzer.h
+++ b/chrome/test/fuzzing/in_process_proto_fuzzer.h
@@ -7,6 +7,7 @@
 #define CHROME_TEST_FUZZING_IN_PROCESS_PROTO_FUZZER_H_
 
 #include "chrome/test/fuzzing/in_process_fuzzer.h"
+#include "testing/libfuzzer/proto/lpm_interface.h"
 
 #define DEFINE_PROTO_FUZZER_IN_PROCESS_IMPL(use_binary, arg)      \
   static void TestOneProtoInput(arg);                             \
@@ -18,9 +19,8 @@
   DEFINE_POST_PROCESS_PROTO_MUTATION_IMPL(FuzzerProtoType)
 
 // Register a text-based proto in process fuzzer.
-// The argument should be a class implementing InProcessFuzzer,
-// which also has a public type something like this:
-//   using FuzzCase = <path to protobuf fuzz case>
+// The argument should be a class implementing InProcessProtoFuzzer,
+// parameterized by the <protobuf fuzz case type>
 #define REGISTER_TEXT_PROTO_IN_PROCESS_FUZZER(arg) \
   REGISTER_IN_PROCESS_FUZZER(arg)                  \
   DEFINE_PROTO_FUZZER_IN_PROCESS_IMPL(false, arg::FuzzCase testcase)
@@ -31,4 +31,23 @@
   REGISTER_IN_PROCESS_FUZZER(arg)                    \
   DEFINE_PROTO_FUZZER_IN_PROCESS_IMPL(true, arg::FuzzCase testcase)
 
+template <typename Proto>
+class InProcessProtoFuzzer : public InProcessFuzzer {
+ public:
+  using FuzzCase = Proto;
+
+ protected:
+  int Fuzz(const uint8_t* data, size_t size) override {
+    using protobuf_mutator::libfuzzer::LoadProtoInput;
+    FuzzCase fuzz_case;
+    if (!LoadProtoInput(false, data, size, &fuzz_case)) {
+      return -1;
+    }
+    return Fuzz(fuzz_case);
+  }
+
+  // Execute a fuzz case using the given input.
+  virtual int Fuzz(const FuzzCase& fuzz_case) = 0;
+};
+
 #endif  // CHROME_TEST_FUZZING_IN_PROCESS_PROTO_FUZZER_H_
diff --git a/chrome/test/fuzzing/page_load_in_process_fuzzer.cc b/chrome/test/fuzzing/page_load_in_process_fuzzer.cc
index 020c373c..d7b36ac 100644
--- a/chrome/test/fuzzing/page_load_in_process_fuzzer.cc
+++ b/chrome/test/fuzzing/page_load_in_process_fuzzer.cc
@@ -3,10 +3,12 @@
 // found in the LICENSE file.
 #include <fuzzer/FuzzedDataProvider.h>
 #include <google/protobuf/descriptor.h>
+#include <memory>
 #include "base/functional/bind.h"
 #include "base/memory/weak_ptr.h"
 #include "base/strings/escape.h"
 #include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
 #include "base/test/bind.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chrome/test/fuzzing/in_process_proto_fuzzer.h"
@@ -14,7 +16,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
-#include "testing/libfuzzer/proto/lpm_interface.h"
+#include "net/test/embedded_test_server/http_response.h"
 
 // A fuzzer which can test the interaction of HTTP response parameters
 // and HTML content. This is a large search space and it's unlikely that
@@ -30,50 +32,77 @@
 //   (We'd need to provide a corpus designed to exercise these).
 // * run servers on 3+ different ports to support cross-origin navigations
 
-class PageLoadInProcessFuzzer : public InProcessFuzzer {
+class PageLoadInProcessFuzzer
+    : public InProcessProtoFuzzer<test::fuzzing::page_load_fuzzing::FuzzCase> {
  public:
-  using FuzzCase = test::fuzzing::page_load_fuzzing::FuzzCase;
+  using WhichServer = test::fuzzing::page_load_fuzzing::WhichServer;
   PageLoadInProcessFuzzer();
 
   void SetUpOnMainThread() override;
-  int Fuzz(const uint8_t* data, size_t size) override;
+  int Fuzz(
+      const test::fuzzing::page_load_fuzzing::FuzzCase& fuzz_case) override;
 
  private:
   static std::unique_ptr<net::test_server::HttpResponse> HandleHTTPRequest(
       base::WeakPtr<PageLoadInProcessFuzzer> fuzzer_weak,
+
+      WhichServer which_server,
       const net::test_server::HttpRequest& request);
+  std::unique_ptr<net::test_server::BasicHttpResponse> DoHandleHTTPRequest(
+      WhichServer which_server,
+      const net::test_server::HttpRequest& request);
+  std::string SubstituteServersInBody(const std::string& body);
+  static void SubstituteServerPattern(std::string* haystack,
+                                      const std::string& pattern,
+                                      const net::EmbeddedTestServer& server);
 
  private:
-  net::EmbeddedTestServer http_test_server_;
-  net::EmbeddedTestServer https_test_server_;
-  std::unique_ptr<net::test_server::BasicHttpResponse> http_response_;
+  // To test cross-origin cases, we have four servers listening
+  // on different ports.
+  net::EmbeddedTestServer http_test_server1_;
+  net::EmbeddedTestServer http_test_server2_;
+  net::EmbeddedTestServer https_test_server1_;
+  net::EmbeddedTestServer https_test_server2_;
+  test::fuzzing::page_load_fuzzing::FuzzCase fuzz_case_;
   base::WeakPtrFactory<PageLoadInProcessFuzzer> weak_ptr_factory_{this};
 };
 
 REGISTER_TEXT_PROTO_IN_PROCESS_FUZZER(PageLoadInProcessFuzzer)
 
 PageLoadInProcessFuzzer::PageLoadInProcessFuzzer()
-    : http_test_server_(net::EmbeddedTestServer::TYPE_HTTP),
-      https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
-  https_test_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
-  http_test_server_.RegisterRequestHandler(
-      base::BindRepeating(&PageLoadInProcessFuzzer::HandleHTTPRequest,
-                          weak_ptr_factory_.GetWeakPtr()));
-  https_test_server_.RegisterRequestHandler(
-      base::BindRepeating(&PageLoadInProcessFuzzer::HandleHTTPRequest,
-                          weak_ptr_factory_.GetWeakPtr()));
+    : http_test_server1_(net::EmbeddedTestServer::TYPE_HTTP),
+      http_test_server2_(net::EmbeddedTestServer::TYPE_HTTP),
+      https_test_server1_(net::EmbeddedTestServer::TYPE_HTTPS),
+      https_test_server2_(net::EmbeddedTestServer::TYPE_HTTPS) {
+  https_test_server1_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
+  https_test_server2_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
+  http_test_server1_.RegisterRequestHandler(base::BindRepeating(
+      &PageLoadInProcessFuzzer::HandleHTTPRequest,
+      weak_ptr_factory_.GetWeakPtr(), WhichServer::HTTP_ORIGIN1));
+  https_test_server1_.RegisterRequestHandler(base::BindRepeating(
+      &PageLoadInProcessFuzzer::HandleHTTPRequest,
+      weak_ptr_factory_.GetWeakPtr(), WhichServer::HTTPS_ORIGIN1));
+  http_test_server2_.RegisterRequestHandler(base::BindRepeating(
+      &PageLoadInProcessFuzzer::HandleHTTPRequest,
+      weak_ptr_factory_.GetWeakPtr(), WhichServer::HTTP_ORIGIN2));
+  https_test_server2_.RegisterRequestHandler(base::BindRepeating(
+      &PageLoadInProcessFuzzer::HandleHTTPRequest,
+      weak_ptr_factory_.GetWeakPtr(), WhichServer::HTTPS_ORIGIN2));
 }
 
 void PageLoadInProcessFuzzer::SetUpOnMainThread() {
   InProcessFuzzer::SetUpOnMainThread();
   host_resolver()->AddRule("*", "127.0.0.1");
-  ASSERT_TRUE(http_test_server_.Start());
-  ASSERT_TRUE(https_test_server_.Start());
+  ASSERT_TRUE(http_test_server1_.Start());
+  ASSERT_TRUE(http_test_server2_.Start());
+  ASSERT_TRUE(https_test_server1_.Start());
+  ASSERT_TRUE(https_test_server2_.Start());
 }
 
 std::unique_ptr<net::test_server::HttpResponse>
 PageLoadInProcessFuzzer::HandleHTTPRequest(
     base::WeakPtr<PageLoadInProcessFuzzer> fuzzer_weak,
+    WhichServer which_server,
     const net::test_server::HttpRequest& request) {
   std::unique_ptr<net::test_server::BasicHttpResponse> response;
   // We are running on the embedded test server's thread.
@@ -85,7 +114,7 @@
       base::BindLambdaForTesting([&]() {
         PageLoadInProcessFuzzer* fuzzer = fuzzer_weak.get();
         if (fuzzer) {
-          response = std::move(fuzzer->http_response_);
+          response = fuzzer->DoHandleHTTPRequest(which_server, request);
         }
         run_loop.Quit();
       });
@@ -94,45 +123,99 @@
   return response;
 }
 
-int PageLoadInProcessFuzzer::Fuzz(const uint8_t* data, size_t size) {
-  FuzzCase fuzz_case;
-  fuzz_case.ParseFromArray(data, size);
+std::unique_ptr<net::test_server::BasicHttpResponse>
+PageLoadInProcessFuzzer::DoHandleHTTPRequest(
+    WhichServer which_server,
+    const net::test_server::HttpRequest& request) {
+  // Look through all the network resources given in the fuzz case and build
+  // a response if we find one.
+  LOG(INFO) << "Got request at " << which_server << " path "
+            << request.relative_url;
+  for (const auto& network_resource : fuzz_case_.network_resource()) {
+    if (network_resource.which_server() == which_server &&
+        (network_resource.path() == request.relative_url ||
+         (request.relative_url == "/" && network_resource.path().empty()))) {
+      std::unique_ptr<net::test_server::BasicHttpResponse> response =
+          std::make_unique<net::test_server::BasicHttpResponse>();
+      response->set_code(
+          static_cast<net::HttpStatusCode>(network_resource.http_status()));
+      response->set_content_type(network_resource.content_type());
+      for (const auto& header : network_resource.custom_headers()) {
+        response->AddCustomHeader(header.key(), header.value());
+      }
+      if (network_resource.has_reason()) {
+        response->set_reason(network_resource.reason());
+      }
+      if (network_resource.has_body()) {
+        response->set_content(SubstituteServersInBody(network_resource.body()));
+      }
+      LOG(INFO) << "Returning valid response for " << which_server << " path "
+                << request.relative_url;
+      return response;
+    }
+  }
+  return nullptr;
+}
+
+int PageLoadInProcessFuzzer::Fuzz(
+    const test::fuzzing::page_load_fuzzing::FuzzCase& fuzz_case) {
+  fuzz_case_ = fuzz_case;
 
   GURL test_url;
 
-  std::string content_type = fuzz_case.content_type();
-  std::string body = fuzz_case.body();
-
-  if (fuzz_case.has_network_load()) {
-    // Load the page over a network. Slow but we can provide lots of options
-    // and realism.
-    const auto& network_load = fuzz_case.network_load();
-    std::string path = network_load.path();
-    if (network_load.https()) {
-      test_url = https_test_server_.GetURL(path);
-    } else {
-      test_url = http_test_server_.GetURL(path);
-    }
-
-    std::unique_ptr<net::test_server::BasicHttpResponse> response =
-        std::make_unique<net::test_server::BasicHttpResponse>();
-
-    // Now figure out what sort of response the server will provide
-    response->set_code(
-        static_cast<net::HttpStatusCode>(network_load.http_status()));
-    response->set_content_type(content_type);
-    for (const auto& header : network_load.custom_headers()) {
-      response->AddCustomHeader(header.key(), header.value());
-    }
-    if (network_load.has_reason()) {
-      response->set_reason(network_load.reason());
-    }
-    http_response_ = std::move(response);
-  } else {
-    // Request via a data: URI which should be quicker.
+  if (fuzz_case_.has_data_uri_navigation()) {
+    const auto& data_uri_navigation = fuzz_case_.data_uri_navigation();
+    std::string content_type = data_uri_navigation.content_type();
+    std::string body = SubstituteServersInBody(data_uri_navigation.body());
+    // Request via a data: URI which should be quickest.
     test_url = GURL(base::StrCat({"data:", content_type, ";charset=utf-8,",
                                   base::EscapeQueryParamValue(body, false)}));
+  } else {
+    // We navigate to the first server resource listed.
+    if (fuzz_case_.network_resource_size() < 1) {
+      return -1;  // invalid fuzz case.
+    }
+    const auto& network_resource = fuzz_case_.network_resource(0);
+    std::string path = network_resource.path();
+    switch (network_resource.which_server()) {
+      case WhichServer::HTTP_ORIGIN1:
+        test_url = http_test_server1_.GetURL(path);
+        break;
+      case WhichServer::HTTP_ORIGIN2:
+        test_url = http_test_server2_.GetURL(path);
+        break;
+      case WhichServer::HTTPS_ORIGIN1:
+        test_url = https_test_server1_.GetURL(path);
+        break;
+      case WhichServer::HTTPS_ORIGIN2:
+        test_url = https_test_server2_.GetURL(path);
+        break;
+      default:
+        LOG(FATAL) << "Unexpected proto value for which server";
+        break;
+    }
   }
+
+  LOG(INFO) << "Navigating to " << test_url;
   base::IgnoreResult(ui_test_utils::NavigateToURL(browser(), test_url));
   return 0;
 }
+
+void PageLoadInProcessFuzzer::SubstituteServerPattern(
+    std::string* body,
+    const std::string& pattern,
+    const net::EmbeddedTestServer& server) {
+  std::string url = server.GetURL("").spec();
+  url.pop_back();  // remove trailing /
+  base::ReplaceSubstringsAfterOffset(body, 0, pattern, url);
+}
+
+std::string PageLoadInProcessFuzzer::SubstituteServersInBody(
+    const std::string& body) {
+  std::string result = body;
+  SubstituteServerPattern(&result, "$HTTP_ORIGIN1", http_test_server1_);
+  SubstituteServerPattern(&result, "$HTTP_ORIGIN2", http_test_server2_);
+  SubstituteServerPattern(&result, "$HTTPS_ORIGIN1", https_test_server1_);
+  SubstituteServerPattern(&result, "$HTTPS_ORIGIN2", https_test_server2_);
+  return result;
+}
diff --git a/chrome/test/fuzzing/page_load_in_process_fuzzer.proto b/chrome/test/fuzzing/page_load_in_process_fuzzer.proto
index 87f135b..18a10ac7 100644
--- a/chrome/test/fuzzing/page_load_in_process_fuzzer.proto
+++ b/chrome/test/fuzzing/page_load_in_process_fuzzer.proto
@@ -7,31 +7,64 @@
 
 // A fuzzer case for page_load_in_process_fuzzer.cc.
 message FuzzCase {
-  // Whether to load the page over the network. If not, a data: URI will be
-  // used.
-  optional NetworkLoad network_load = 1;
-  // Content-Type header.
-  string content_type = 2;
+  // Any network (HTTP, HTTPS) resources that should be available
+  repeated NetworkResource network_resource = 1;
+  // Where to navigate the browser initially
+  // If present, we navigate to a data URI with a particular content.
+  // Otherwise we navigate to the first network resource.
+  optional DataUriNavigation data_uri_navigation = 2;
   // Body of the response.
   string body = 3;
 }
 
 // Additional information if we're loading the resource over the network.
-message NetworkLoad {
-  // Whether to use https.
-  bool https = 1;
-  // The path to request on the http(s) server
+message NetworkResource {
+  // Which server will this resource be made available on.
+  // Typically, use HTTPS_ORIGIN1, but if you need to test a cross-origin
+  // case you can make resources available on other servers.
+  WhichServer which_server = 1;
+  // The path to request on the http(s) server. Should start with /
   string path = 2;
   // The HTTP status which will be presented in the response.
   uint32 http_status = 3;
   // Custom headers in the HTTP response.
   repeated CustomHeader custom_headers = 4;
+  // The Content-Type to be returned.
+  string content_type = 5;
   // The textual reason string to go along with the HTTP status code.
-  optional string reason = 5;
+  optional string reason = 6;
+  // The body of the response.
+  // The strings $HTTPS_ORIGIN1, $HTTPS_ORIGIN2, $HTTP_ORIGIN1 and $HTTP_ORIGIN2
+  // will be substituted with the root URIs for the test servers.
+  // For example, "<html><img src=\"$HTTPS_ORIGIN1/test.png\"></html>"
+  // should load a page from one of the network_resources provided in
+  // the FuzzCase. Four servers are provided to test cross-origin cases.
+  optional string body = 7;
 }
 
-// A custom header in an HTTP request.
+// Which server should this network resource be made available on.
+enum WhichServer {
+  HTTPS_ORIGIN1 = 0;
+  HTTPS_ORIGIN2 = 1;
+  HTTP_ORIGIN1 = 2;
+  HTTP_ORIGIN2 = 3;
+}
+
+// A custom header in an HTTP response.
 message CustomHeader {
   string key = 1;
   string value = 2;
 }
+
+// A navigation to a data: URI
+message DataUriNavigation {
+  // The content-type of the data URI
+  string content_type = 1;
+  // The body, which will be base64 encoded.
+  // The strings $HTTPS_ORIGIN1, $HTTPS_ORIGIN2, $HTTP_ORIGIN1 and $HTTP_ORIGIN2
+  // will be substituted with the root URIs for the test servers.
+  // For example, "<html><img src=\"$HTTPS_ORIGIN1/test.png\"></html>"
+  // should load a page from one of the network_resources provided in
+  // the FuzzCase. Four servers are provided to test cross-origin cases.
+  string body = 2;
+}
diff --git a/chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/cross_origin.textproto b/chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/cross_origin.textproto
new file mode 100644
index 0000000..6c26d78d
--- /dev/null
+++ b/chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/cross_origin.textproto
@@ -0,0 +1,17 @@
+
+network_resource {
+  which_server: HTTPS_ORIGIN1
+  path: "/foo.html"
+  http_status: 200
+  content_type: "text/html"
+  reason: "OK"
+  body: "<html><head><title>Page</title><body><p>Hello!</p><iframe src=\"$HTTPS_ORIGIN2/bar.html\"></iframe></body></html>"
+}
+network_resource {
+  which_server: HTTPS_ORIGIN2
+  path: "/bar.html"
+  http_status: 200
+  content_type: "text/html"
+  reason: "OK"
+  body: "<html><body>Cross-origin iframe!</body></html>"
+}
diff --git a/chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/network.textproto b/chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/network.textproto
new file mode 100644
index 0000000..9ca670e
--- /dev/null
+++ b/chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/network.textproto
@@ -0,0 +1,7 @@
+
+network_resource {
+  path: "/foo.html"
+  http_status: 200
+  content_type: "text/html"
+  body: "<html><head><title>Page</title><script type=\"text/javascript\">function greet() {console.log(\"HellOOO\") }</script></head><body onload=\"greet()\"><h1>Hello!</h1></body></html>"
+}
\ No newline at end of file
diff --git a/chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/simple.textproto b/chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/simple.textproto
new file mode 100644
index 0000000..d83f3c7
--- /dev/null
+++ b/chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/simple.textproto
@@ -0,0 +1,4 @@
+data_uri_navigation {
+    content_type: "text/html"
+    body: "<html><head><title>Page</title><body><p>Hello!</p></body></html>"
+}
diff --git a/chromeos/ash/components/auth_panel/factor_auth_view_factory.cc b/chromeos/ash/components/auth_panel/factor_auth_view_factory.cc
index 924e9c42..a02b44a 100644
--- a/chromeos/ash/components/auth_panel/factor_auth_view_factory.cc
+++ b/chromeos/ash/components/auth_panel/factor_auth_view_factory.cc
@@ -27,6 +27,8 @@
       return nullptr;
     case AshAuthFactor::kLegacyFingerprint:
       return nullptr;
+    case AshAuthFactor::kLocalPassword:
+      return nullptr;
   }
 }
 
diff --git a/chromeos/ash/components/growth/BUILD.gn b/chromeos/ash/components/growth/BUILD.gn
index bf39076..e685716 100644
--- a/chromeos/ash/components/growth/BUILD.gn
+++ b/chromeos/ash/components/growth/BUILD.gn
@@ -14,6 +14,8 @@
     "campaigns_matcher.h",
     "campaigns_model.cc",
     "campaigns_model.h",
+    "growth_metrics.cc",
+    "growth_metrics.h",
   ]
 
   deps = [
diff --git a/chromeos/ash/components/growth/campaigns_manager.cc b/chromeos/ash/components/growth/campaigns_manager.cc
index 152fb5a..b995735 100644
--- a/chromeos/ash/components/growth/campaigns_manager.cc
+++ b/chromeos/ash/components/growth/campaigns_manager.cc
@@ -10,6 +10,7 @@
 #include "base/logging.h"
 #include "base/task/thread_pool.h"
 #include "chromeos/ash/components/growth/campaigns_matcher.h"
+#include "chromeos/ash/components/growth/growth_metrics.h"
 #include "components/prefs/pref_service.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -28,13 +29,14 @@
           campaigns_component_path.Append(kCampaignFileName),
           &campaigns_data)) {
     LOG(ERROR) << "Failed to read campaigns file from disk.";
+    RecordCampaignsManagerError(CampaignsManagerError::kCampaignsFileLoadFail);
     return absl::nullopt;
   }
 
   absl::optional<base::Value> value(base::JSONReader::Read(campaigns_data));
   if (!value || !value->is_dict()) {
-    // TODO(b/299305911): Add metrics to track fail parsing campaign file.
     LOG(ERROR) << "Failed to parse campaigns file: " << campaigns_data;
+    RecordCampaignsManagerError(CampaignsManagerError::kCampaignsParsingFail);
     return absl::nullopt;
   }
   return std::move(value->GetDict());
@@ -91,7 +93,8 @@
     const absl::optional<const base::FilePath>& path) {
   if (!path.has_value()) {
     LOG(ERROR) << "Failed to load campaign component.";
-    // TODO(b/299305911): Add metrics to track fail loading campaigns component.
+    RecordCampaignsManagerError(
+        CampaignsManagerError::kCampaignsComponentLoadFail);
     OnCampaignsLoaded(/*campaigns=*/absl::nullopt);
     return;
   }
diff --git a/chromeos/ash/components/growth/campaigns_manager_unittest.cc b/chromeos/ash/components/growth/campaigns_manager_unittest.cc
index e0c3a1e9..fe65e63 100644
--- a/chromeos/ash/components/growth/campaigns_manager_unittest.cc
+++ b/chromeos/ash/components/growth/campaigns_manager_unittest.cc
@@ -11,10 +11,12 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "base/version.h"
 #include "chromeos/ash/components/growth/campaigns_model.h"
+#include "chromeos/ash/components/growth/growth_metrics.h"
 #include "chromeos/ash/components/growth/mock_campaigns_manager_client.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
@@ -75,6 +77,9 @@
 
 constexpr char kCampaignsFileName[] = "campaigns.json";
 
+inline constexpr char kCampaignsManagerErrorHistogramName[] =
+    "Ash.Growth.CampaignsManager.Error";
+
 // testing::InvokeArgument<N> does not work with base::OnceCallback. Use this
 // gmock action template to invoke base::OnceCallback. `k` is the k-th argument
 // and `T` is the callback's type.
@@ -225,6 +230,8 @@
 };
 
 TEST_F(CampaignsManagerTest, LoadAndGetDemoModeCampaign) {
+  base::HistogramTester histogram_tester;
+
   LoadComponentAndVerifyLoadComplete(
       base::StringPrintf(kValidCampaignsFileTemplate, kValidDemoModeTargeting));
 
@@ -238,6 +245,14 @@
 
   VerifyDemoModePayload(
       campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
+
+  histogram_tester.ExpectBucketCount(kCampaignsManagerErrorHistogramName,
+                                     CampaignsManagerError::kInvalidTargeting,
+                                     /*count=*/1);
+
+  histogram_tester.ExpectBucketCount(kCampaignsManagerErrorHistogramName,
+                                     CampaignsManagerError::kInvalidCampaign,
+                                     /*count=*/1);
 }
 
 TEST_F(CampaignsManagerTest, GetCampaignNoTargeting) {
@@ -560,6 +575,7 @@
 }
 
 TEST_F(CampaignsManagerTest, LoadCampaignsFailed) {
+  base::HistogramTester histogram_tester;
   TestCampaignsManagerObserver observer;
   campaigns_manager_->AddObserver(&observer);
 
@@ -575,9 +591,39 @@
   ASSERT_TRUE(observer.load_completed());
 
   ASSERT_EQ(nullptr, campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
+
+  histogram_tester.ExpectBucketCount(
+      kCampaignsManagerErrorHistogramName,
+      CampaignsManagerError::kCampaignsComponentLoadFail,
+      /*count=*/1);
+}
+
+TEST_F(CampaignsManagerTest, LoadCampaignsNoFile) {
+  base::HistogramTester histogram_tester;
+  TestCampaignsManagerObserver observer;
+  campaigns_manager_->AddObserver(&observer);
+
+  ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+  EXPECT_CALL(mock_client_, LoadCampaignsComponent(_))
+      .WillOnce(InvokeCallbackArgument<0, CampaignComponentLoadedCallback>(
+          temp_dir_.GetPath()));
+
+  campaigns_manager_->LoadCampaigns();
+  observer.Wait();
+
+  ASSERT_TRUE(observer.load_completed());
+
+  ASSERT_EQ(nullptr, campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
+
+  histogram_tester.ExpectBucketCount(
+      kCampaignsManagerErrorHistogramName,
+      CampaignsManagerError::kCampaignsFileLoadFail,
+      /*count=*/1);
 }
 
 TEST_F(CampaignsManagerTest, LoadCampaignsInvalidFile) {
+  base::HistogramTester histogram_tester;
   TestCampaignsManagerObserver observer;
   campaigns_manager_->AddObserver(&observer);
 
@@ -596,6 +642,11 @@
   ASSERT_TRUE(observer.load_completed());
 
   ASSERT_EQ(nullptr, campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
+
+  histogram_tester.ExpectBucketCount(
+      kCampaignsManagerErrorHistogramName,
+      CampaignsManagerError::kCampaignsParsingFail,
+      /*count=*/1);
 }
 
 TEST_F(CampaignsManagerTest, LoadCampaignsEmptyFile) {
@@ -714,7 +765,7 @@
   ASSERT_EQ(nullptr, campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
 }
 
-TEST_F(CampaignsManagerTest, GetSchedulingCampiagn) {
+TEST_F(CampaignsManagerTest, GetSchedulingCampaign) {
   const auto now = base::Time::Now();
   auto start = now;
   auto end = now + base::Seconds(5);
@@ -726,7 +777,7 @@
       campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
 }
 
-TEST_F(CampaignsManagerTest, GetSchedulingCampiagnMultipleSchedulings) {
+TEST_F(CampaignsManagerTest, GetSchedulingCampaignMultipleSchedulings) {
   const auto now = base::Time::Now();
   // First scheduling start and end before now.
   auto start = now - base::Seconds(10);
@@ -754,7 +805,7 @@
       campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
 }
 
-TEST_F(CampaignsManagerTest, GetSchedulingCampiagnMismatch) {
+TEST_F(CampaignsManagerTest, GetSchedulingCampaignMismatch) {
   const auto now = base::Time::Now();
   auto start = now + base::Seconds(5);
   auto end = now + base::Seconds(10);
@@ -765,7 +816,7 @@
   ASSERT_EQ(nullptr, campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
 }
 
-TEST_F(CampaignsManagerTest, GetSchedulingCampiagnStartOnly) {
+TEST_F(CampaignsManagerTest, GetSchedulingCampaignStartOnly) {
   const auto now = base::Time::Now();
   LoadComponentWithScheduling(
       base::StringPrintf(R"([{"start": %f}])", now.InSecondsFSinceUnixEpoch()));
@@ -774,7 +825,7 @@
       campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
 }
 
-TEST_F(CampaignsManagerTest, GetSchedulingCampiagnStartOnlyMismatch) {
+TEST_F(CampaignsManagerTest, GetSchedulingCampaignStartOnlyMismatch) {
   const auto now = base::Time::Now();
   auto start = now + base::Seconds(5);
   LoadComponentWithScheduling(base::StringPrintf(
@@ -783,7 +834,7 @@
   ASSERT_EQ(nullptr, campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
 }
 
-TEST_F(CampaignsManagerTest, GetSchedulingCampiagnEndOnly) {
+TEST_F(CampaignsManagerTest, GetSchedulingCampaignEndOnly) {
   const auto now = base::Time::Now();
   auto end = now + base::Seconds(5);
   LoadComponentWithScheduling(
@@ -793,7 +844,7 @@
       campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
 }
 
-TEST_F(CampaignsManagerTest, GetSchedulingCampiagnEndOnlyMismatch) {
+TEST_F(CampaignsManagerTest, GetSchedulingCampaignEndOnlyMismatch) {
   const auto now = base::Time::Now();
   auto end = now - base::Seconds(10);
   LoadComponentWithScheduling(
@@ -802,7 +853,7 @@
   ASSERT_EQ(nullptr, campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
 }
 
-TEST_F(CampaignsManagerTest, GetSchedulingCampiagnInvalidTargeting) {
+TEST_F(CampaignsManagerTest, GetSchedulingCampaignInvalidTargeting) {
   LoadComponentWithScheduling("test");
 
   ASSERT_EQ(nullptr, campaigns_manager_->GetCampaignBySlot(Slot::kDemoModeApp));
diff --git a/chromeos/ash/components/growth/campaigns_matcher.cc b/chromeos/ash/components/growth/campaigns_matcher.cc
index 2f32e05..77aa4cb 100644
--- a/chromeos/ash/components/growth/campaigns_matcher.cc
+++ b/chromeos/ash/components/growth/campaigns_matcher.cc
@@ -11,6 +11,7 @@
 #include "base/version.h"
 #include "chromeos/ash/components/growth/campaigns_manager_client.h"
 #include "chromeos/ash/components/growth/campaigns_model.h"
+#include "chromeos/ash/components/growth/growth_metrics.h"
 #include "components/prefs/pref_service.h"
 #include "components/version_info/version_info.h"
 
@@ -21,8 +22,9 @@
                base::StringPiece pref_path,
                const PrefService* pref_service) {
   if (!pref_service) {
-    // TODO(b/299305911): This is unexpected. Add metrics to track this case.
     LOG(ERROR) << "Matching pref before pref service is available";
+    RecordCampaignsManagerError(
+        CampaignsManagerError::kUserPrefUnavailableAtMatching);
     return false;
   }
 
@@ -97,6 +99,7 @@
     const auto* campaign = campaign_value.GetIfDict();
     if (!campaign) {
       LOG(ERROR) << "Invalid campaign.";
+      RecordCampaignsManagerError(CampaignsManagerError::kInvalidCampaign);
       continue;
     }
 
@@ -226,8 +229,8 @@
   const auto* targeting = targetings->front().GetIfDict();
   if (!targeting) {
     // Targeting is invalid. Skip the current campaign.
-    // TODO(b/299305911): Add metrics to track when a targeting is invalid.
     LOG(ERROR) << "Invalid targeting.";
+    RecordCampaignsManagerError(CampaignsManagerError::kInvalidTargeting);
     return false;
   }
 
diff --git a/chromeos/ash/components/growth/growth_metrics.cc b/chromeos/ash/components/growth/growth_metrics.cc
new file mode 100644
index 0000000..55cbc69
--- /dev/null
+++ b/chromeos/ash/components/growth/growth_metrics.cc
@@ -0,0 +1,22 @@
+// 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 "chromeos/ash/components/growth/growth_metrics.h"
+
+#include "base/metrics/histogram_functions.h"
+
+namespace growth {
+
+namespace {
+
+inline constexpr char kCampaignsManagerErrorHistogramName[] =
+    "Ash.Growth.CampaignsManager.Error";
+
+}  // namespace
+
+void RecordCampaignsManagerError(CampaignsManagerError error) {
+  base::UmaHistogramEnumeration(kCampaignsManagerErrorHistogramName, error);
+}
+
+}  // namespace growth
diff --git a/chromeos/ash/components/growth/growth_metrics.h b/chromeos/ash/components/growth/growth_metrics.h
new file mode 100644
index 0000000..0a0221be
--- /dev/null
+++ b/chromeos/ash/components/growth/growth_metrics.h
@@ -0,0 +1,30 @@
+// 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 CHROMEOS_ASH_COMPONENTS_GROWTH_GROWTH_METRICS_H_
+#define CHROMEOS_ASH_COMPONENTS_GROWTH_GROWTH_METRICS_H_
+
+namespace growth {
+
+// These enum values represent user-facing errors in the campaigns loading and
+// matching flow. Entries should not be renumbered and numeric values should
+// never be reused. Please keep in sync with "CampaignsManagerError" in
+// src/tools/metrics/histograms/enums.xml.
+enum class CampaignsManagerError {
+  kCampaignsComponentLoadFail = 0,
+  kCampaignsFileLoadFail = 1,
+  kCampaignsParsingFail = 2,
+  kUserPrefUnavailableAtMatching = 3,
+  kInvalidCampaign = 4,
+  kInvalidTargeting = 5,
+
+  kMaxValue = kInvalidTargeting,
+};
+
+// Records errors encountered during the campaigns loading and matching flow.
+void RecordCampaignsManagerError(CampaignsManagerError error_code);
+
+}  // namespace growth
+
+#endif  // CHROMEOS_ASH_COMPONENTS_GROWTH_GROWTH_METRICS_H_
diff --git a/chromeos/ash/components/login/auth/public/user_context.h b/chromeos/ash/components/login/auth/public/user_context.h
index fb4caff..b6a8c708 100644
--- a/chromeos/ash/components/login/auth/public/user_context.h
+++ b/chromeos/ash/components/login/auth/public/user_context.h
@@ -10,6 +10,7 @@
 #include "base/component_export.h"
 #include "base/containers/enum_set.h"
 #include "base/time/time.h"
+#include "chromeos/ash/components/cryptohome/auth_factor.h"
 #include "chromeos/ash/components/login/auth/public/auth_factors_configuration.h"
 #include "chromeos/ash/components/login/auth/public/auth_session_intent.h"
 #include "chromeos/ash/components/login/auth/public/auth_types.h"
diff --git a/chromeos/ash/components/osauth/public/common_types.h b/chromeos/ash/components/osauth/public/common_types.h
index 06b76af..c38cd34 100644
--- a/chromeos/ash/components/osauth/public/common_types.h
+++ b/chromeos/ash/components/osauth/public/common_types.h
@@ -56,7 +56,8 @@
   kRecovery = 4,
   kLegacyPin = 5,
   kLegacyFingerprint = 6,
-  kMaxValue = kLegacyFingerprint,
+  kLocalPassword = 7,
+  kMaxValue = kLocalPassword,
 };
 
 using AuthFactorsSet = base::EnumSet<AshAuthFactor,
diff --git a/chromeos/ash/components/osauth/public/string_utils.cc b/chromeos/ash/components/osauth/public/string_utils.cc
index 6f1f715d..ce18e266 100644
--- a/chromeos/ash/components/osauth/public/string_utils.cc
+++ b/chromeos/ash/components/osauth/public/string_utils.cc
@@ -32,6 +32,7 @@
     PRINT(Recovery)
     PRINT(LegacyPin)
     PRINT(LegacyFingerprint)
+    PRINT(LocalPassword)
 #undef PRINT
   }
 }
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 98e9c3d..6b4ace9 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -130,6 +130,13 @@
 // controls all system UI updates and new system components. go/jelly-flags
 BASE_FEATURE(kJellyroll, "Jellyroll", base::FEATURE_ENABLED_BY_DEFAULT);
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// Enables Kiosk Heartbeats to be sent via Encrypted Reporting Pipeline
+BASE_FEATURE(kKioskHeartbeatsViaERP,
+             "KioskHeartbeatsViaERP",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 // Controls enabling / disabling the orca feature.
 BASE_FEATURE(kOrca, "Orca", base::FEATURE_DISABLED_BY_DEFAULT);
 
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 7650eb4..8604265 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -60,6 +60,10 @@
 BASE_DECLARE_FEATURE(kIWAForTelemetryExtensionAPI);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) BASE_DECLARE_FEATURE(kJelly);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) BASE_DECLARE_FEATURE(kJellyroll);
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+BASE_DECLARE_FEATURE(kKioskHeartbeatsViaERP);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) BASE_DECLARE_FEATURE(kOrca);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) BASE_DECLARE_FEATURE(kOrcaDogfood);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
diff --git a/clank b/clank
index 037ef38..0f952cb 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 037ef38c3ed595b087c9a5340ebba24feb6c4956
+Subproject commit 0f952cb0548617cdb61a432963ef1b3b57beac10
diff --git a/components/attribution_reporting/aggregatable_dedup_key.h b/components/attribution_reporting/aggregatable_dedup_key.h
index 8a7bfb7..3efac7e 100644
--- a/components/attribution_reporting/aggregatable_dedup_key.h
+++ b/components/attribution_reporting/aggregatable_dedup_key.h
@@ -33,6 +33,9 @@
   AggregatableDedupKey(absl::optional<uint64_t> dedup_key, FilterPair);
 
   base::Value::Dict ToJson() const;
+
+  friend bool operator==(const AggregatableDedupKey&,
+                         const AggregatableDedupKey&) = default;
 };
 
 }  // namespace attribution_reporting
diff --git a/components/attribution_reporting/aggregatable_trigger_data.h b/components/attribution_reporting/aggregatable_trigger_data.h
index 1c580d4..91b9fb3 100644
--- a/components/attribution_reporting/aggregatable_trigger_data.h
+++ b/components/attribution_reporting/aggregatable_trigger_data.h
@@ -48,6 +48,9 @@
 
   base::Value::Dict ToJson() const;
 
+  friend bool operator==(const AggregatableTriggerData&,
+                         const AggregatableTriggerData&) = default;
+
  private:
   AggregatableTriggerData(absl::uint128 key_piece,
                           Keys source_keys,
diff --git a/components/attribution_reporting/aggregatable_values.h b/components/attribution_reporting/aggregatable_values.h
index f885558..d445713 100644
--- a/components/attribution_reporting/aggregatable_values.h
+++ b/components/attribution_reporting/aggregatable_values.h
@@ -41,6 +41,9 @@
 
   base::Value::Dict ToJson() const;
 
+  friend bool operator==(const AggregatableValues&,
+                         const AggregatableValues&) = default;
+
  private:
   explicit AggregatableValues(Values);
 
diff --git a/components/attribution_reporting/aggregation_keys.h b/components/attribution_reporting/aggregation_keys.h
index 9006be33..271b316e 100644
--- a/components/attribution_reporting/aggregation_keys.h
+++ b/components/attribution_reporting/aggregation_keys.h
@@ -40,6 +40,9 @@
 
   base::Value::Dict ToJson() const;
 
+  friend bool operator==(const AggregationKeys&,
+                         const AggregationKeys&) = default;
+
  private:
   explicit AggregationKeys(Keys keys);
 
diff --git a/components/attribution_reporting/destination_set.h b/components/attribution_reporting/destination_set.h
index 8f706177..7301209 100644
--- a/components/attribution_reporting/destination_set.h
+++ b/components/attribution_reporting/destination_set.h
@@ -53,6 +53,9 @@
 
   base::Value ToJson() const;
 
+  friend bool operator==(const DestinationSet&,
+                         const DestinationSet&) = default;
+
  private:
   explicit DestinationSet(Destinations);
 
diff --git a/components/attribution_reporting/event_report_windows.h b/components/attribution_reporting/event_report_windows.h
index f6cd649..165ce42 100644
--- a/components/attribution_reporting/event_report_windows.h
+++ b/components/attribution_reporting/event_report_windows.h
@@ -83,6 +83,9 @@
 
   void Serialize(base::Value::Dict& dict) const;
 
+  friend bool operator==(const EventReportWindows&,
+                         const EventReportWindows&) = default;
+
  private:
   EventReportWindows(base::TimeDelta start_time,
                      base::flat_set<base::TimeDelta> end_times);
diff --git a/components/attribution_reporting/event_trigger_data.h b/components/attribution_reporting/event_trigger_data.h
index 291dec9..4766e87c 100644
--- a/components/attribution_reporting/event_trigger_data.h
+++ b/components/attribution_reporting/event_trigger_data.h
@@ -47,6 +47,9 @@
                    FilterPair);
 
   base::Value::Dict ToJson() const;
+
+  friend bool operator==(const EventTriggerData&,
+                         const EventTriggerData&) = default;
 };
 
 }  // namespace attribution_reporting
diff --git a/components/attribution_reporting/filters.h b/components/attribution_reporting/filters.h
index 7772426..376a6c8 100644
--- a/components/attribution_reporting/filters.h
+++ b/components/attribution_reporting/filters.h
@@ -64,6 +64,8 @@
                          const FiltersDisjunction&,
                          bool negated) const;
 
+  friend bool operator==(const FilterData&, const FilterData&) = default;
+
  private:
   explicit FilterData(FilterValues);
 
@@ -96,6 +98,8 @@
       base::Value::Dict&);
 
   void SerializeIfNotEmpty(base::Value::Dict&) const;
+
+  friend bool operator==(const FilterPair&, const FilterPair&) = default;
 };
 
 class COMPONENT_EXPORT(ATTRIBUTION_REPORTING) FilterConfig {
@@ -119,8 +123,11 @@
   const absl::optional<base::TimeDelta>& lookback_window() const {
     return lookback_window_;
   }
+
   const FilterValues& filter_values() const { return filter_values_; }
 
+  friend bool operator==(const FilterConfig&, const FilterConfig&) = default;
+
  private:
   explicit FilterConfig(FilterValues,
                         absl::optional<base::TimeDelta> lookback_window);
diff --git a/components/attribution_reporting/os_registration.h b/components/attribution_reporting/os_registration.h
index 06f2b0e..e47c3cf 100644
--- a/components/attribution_reporting/os_registration.h
+++ b/components/attribution_reporting/os_registration.h
@@ -17,6 +17,9 @@
 struct COMPONENT_EXPORT(ATTRIBUTION_REPORTING) OsRegistrationItem {
   GURL url;
   bool debug_reporting = false;
+
+  friend bool operator==(const OsRegistrationItem&,
+                         const OsRegistrationItem&) = default;
 };
 
 // Parses an Attribution-Reporting-OS-Source or
diff --git a/components/attribution_reporting/source_registration.h b/components/attribution_reporting/source_registration.h
index 5128db6..6e9134b 100644
--- a/components/attribution_reporting/source_registration.h
+++ b/components/attribution_reporting/source_registration.h
@@ -57,6 +57,9 @@
   bool IsValid() const;
   bool IsValidForSourceType(mojom::SourceType) const;
 
+  friend bool operator==(const SourceRegistration&,
+                         const SourceRegistration&) = default;
+
   uint64_t source_event_id = 0;
   DestinationSet destination_set;
   // These `base::TimeDelta`s must be non-negative if set. This is verified by
diff --git a/components/attribution_reporting/suitable_origin.cc b/components/attribution_reporting/suitable_origin.cc
index 9d4ac7c..ebabedd 100644
--- a/components/attribution_reporting/suitable_origin.cc
+++ b/components/attribution_reporting/suitable_origin.cc
@@ -66,12 +66,6 @@
 
 SuitableOrigin& SuitableOrigin::operator=(SuitableOrigin&&) = default;
 
-bool SuitableOrigin::operator<(const SuitableOrigin& other) const {
-  DCHECK(IsValid());
-  DCHECK(other.IsValid());
-  return origin_ < other.origin_;
-}
-
 std::string SuitableOrigin::Serialize() const {
   DCHECK(IsValid());
   return origin_.Serialize();
diff --git a/components/attribution_reporting/suitable_origin.h b/components/attribution_reporting/suitable_origin.h
index a0dffbc..69709b6d 100644
--- a/components/attribution_reporting/suitable_origin.h
+++ b/components/attribution_reporting/suitable_origin.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_ATTRIBUTION_REPORTING_SUITABLE_ORIGIN_H_
 #define COMPONENTS_ATTRIBUTION_REPORTING_SUITABLE_ORIGIN_H_
 
+#include <compare>
 #include <string>
 #include <utility>
 
@@ -91,7 +92,8 @@
   }
 
   // Allows this type to be used as a key in a set or map.
-  bool operator<(const SuitableOrigin&) const;
+  friend std::weak_ordering operator<=>(const SuitableOrigin&,
+                                        const SuitableOrigin&) = default;
 
   std::string Serialize() const;
 
diff --git a/components/attribution_reporting/suitable_origin_unittest.cc b/components/attribution_reporting/suitable_origin_unittest.cc
index 963f97d..27c9c8f 100644
--- a/components/attribution_reporting/suitable_origin_unittest.cc
+++ b/components/attribution_reporting/suitable_origin_unittest.cc
@@ -121,13 +121,21 @@
   }
 }
 
-TEST(SuitableOriginTest, OperatorLt) {
+TEST(SuitableOriginTest, Comparison) {
   const auto origin_a = SuitableOrigin::Deserialize("https://a.test");
   const auto origin_b = SuitableOrigin::Deserialize("https://b.test");
 
-  EXPECT_TRUE(origin_a < origin_b);
-  EXPECT_FALSE(origin_b < origin_a);
-  EXPECT_FALSE(origin_a < origin_a);
+  EXPECT_LT(origin_a, origin_b);
+  EXPECT_GT(origin_b, origin_a);
+
+  EXPECT_LE(origin_a, origin_b);
+  EXPECT_LE(origin_a, origin_a);
+
+  EXPECT_GE(origin_b, origin_a);
+  EXPECT_GE(origin_b, origin_b);
+
+  EXPECT_EQ(origin_a, origin_a);
+  EXPECT_NE(origin_a, origin_b);
 }
 
 TEST(SuitableOriginTest, IsSitePotentiallySuitable) {
diff --git a/components/attribution_reporting/test_utils.cc b/components/attribution_reporting/test_utils.cc
index ddcfd08..b76eee5 100644
--- a/components/attribution_reporting/test_utils.cc
+++ b/components/attribution_reporting/test_utils.cc
@@ -6,9 +6,7 @@
 
 #include <ostream>
 #include <string>
-#include <tuple>
 
-#include "base/ranges/algorithm.h"
 #include "base/time/time.h"
 #include "base/values.h"
 #include "components/attribution_reporting/aggregatable_dedup_key.h"
@@ -44,53 +42,26 @@
       lookback_window)};
 }
 
-bool operator==(const AggregationKeys& a, const AggregationKeys& b) {
-  return a.keys() == b.keys();
-}
-
 std::ostream& operator<<(std::ostream& out,
                          const AggregationKeys& aggregation_keys) {
   return out << aggregation_keys.ToJson();
 }
 
-bool operator==(const FilterData& a, const FilterData& b) {
-  return a.filter_values() == b.filter_values();
-}
-
-bool operator==(const FilterConfig& a, const FilterConfig& b) {
-  auto tie = [](const FilterConfig& c) {
-    return std::make_tuple(c.filter_values(), c.lookback_window());
-  };
-  return tie(a) == tie(b);
-}
-
 std::ostream& operator<<(std::ostream& out, const FilterData& filter_data) {
   return out << filter_data.ToJson();
 }
 
-bool operator==(const FilterPair& a, const FilterPair& b) {
-  return a.positive == b.positive && a.negative == b.negative;
-}
-
 std::ostream& operator<<(std::ostream& out, const FilterPair& filters) {
   base::Value::Dict dict;
   filters.SerializeIfNotEmpty(dict);
   return out << dict;
 }
 
-bool operator==(const DestinationSet& a, const DestinationSet& b) {
-  return a.destinations() == b.destinations();
-}
-
 std::ostream& operator<<(std::ostream& out,
                          const DestinationSet& destination_set) {
   return out << destination_set.ToJson();
 }
 
-bool operator==(const EventReportWindows& a, const EventReportWindows& b) {
-  return a.start_time() == b.start_time() && a.end_times() == b.end_times();
-}
-
 std::ostream& operator<<(std::ostream& out,
                          const EventReportWindows& event_report_windows) {
   base::Value::Dict dict;
@@ -98,123 +69,52 @@
   return out << dict;
 }
 
-bool operator==(const SourceRegistration& a, const SourceRegistration& b) {
-  auto tie = [](const SourceRegistration& s) {
-    return std::make_tuple(s.source_event_id, s.destination_set, s.expiry,
-                           s.event_report_windows, s.aggregatable_report_window,
-                           s.priority, s.filter_data, s.debug_key,
-                           s.aggregation_keys, s.debug_reporting,
-                           s.max_event_level_reports, s.trigger_config);
-  };
-  return tie(a) == tie(b);
-}
-
 std::ostream& operator<<(std::ostream& out, const SourceRegistration& s) {
   return out << s.ToJson();
 }
 
-bool operator==(const AggregatableValues& a, const AggregatableValues& b) {
-  return a.values() == b.values();
-}
-
 std::ostream& operator<<(std::ostream& out, const AggregatableValues& values) {
   return out << values.ToJson();
 }
 
-bool operator==(const AggregatableTriggerData& a,
-                const AggregatableTriggerData& b) {
-  const auto tie = [](const AggregatableTriggerData& trigger_data) {
-    return std::make_tuple(trigger_data.key_piece(), trigger_data.source_keys(),
-                           trigger_data.filters());
-  };
-  return tie(a) == tie(b);
-}
-
 std::ostream& operator<<(std::ostream& out,
                          const AggregatableTriggerData& trigger_data) {
   return out << trigger_data.ToJson();
 }
 
-bool operator==(const EventTriggerData& a, const EventTriggerData& b) {
-  const auto tie = [](const EventTriggerData& t) {
-    return std::make_tuple(t.data, t.priority, t.dedup_key, t.filters);
-  };
-  return tie(a) == tie(b);
-}
-
 std::ostream& operator<<(std::ostream& out,
                          const EventTriggerData& event_trigger) {
   return out << event_trigger.ToJson();
 }
 
-bool operator==(const TriggerRegistration& a, const TriggerRegistration& b) {
-  auto tie = [](const TriggerRegistration& reg) {
-    return std::make_tuple(reg.filters, reg.debug_key,
-                           reg.aggregatable_dedup_keys, reg.event_triggers,
-                           reg.aggregatable_trigger_data,
-                           reg.aggregatable_values, reg.debug_reporting,
-                           reg.aggregation_coordinator_origin,
-                           reg.source_registration_time_config);
-  };
-  return tie(a) == tie(b);
-}
-
 std::ostream& operator<<(std::ostream& out, const TriggerRegistration& reg) {
   return out << reg.ToJson();
 }
 
-bool operator==(const SuitableOrigin& a, const SuitableOrigin& b) {
-  return *a == *b;
-}
-
 std::ostream& operator<<(std::ostream& out, const SuitableOrigin& origin) {
   return out << *origin;
 }
 
-bool operator==(const AggregatableDedupKey& a, const AggregatableDedupKey& b) {
-  const auto tie = [](const AggregatableDedupKey& t) {
-    return std::make_tuple(t.dedup_key, t.filters);
-  };
-  return tie(a) == tie(b);
-}
-
 std::ostream& operator<<(std::ostream& out,
                          const AggregatableDedupKey& aggregatable_dedup_key) {
   return out << aggregatable_dedup_key.ToJson();
 }
 
-bool operator==(const OsRegistrationItem& a, const OsRegistrationItem& b) {
-  return std::tie(a.url, a.debug_reporting) ==
-         std::tie(b.url, b.debug_reporting);
-}
-
 std::ostream& operator<<(std::ostream& out, const OsRegistrationItem& item) {
   return out << "{url=" << item.url
              << ", debug_reporting=" << item.debug_reporting << "}";
 }
 
-bool operator==(const TriggerConfig& a, const TriggerConfig& b) {
-  return a.trigger_data_matching() == b.trigger_data_matching();
-}
-
 std::ostream& operator<<(std::ostream& out, const TriggerConfig& config) {
   base::Value::Dict dict;
   config.SerializeForTesting(dict);
   return out << dict;
 }
 
-bool operator==(const TriggerSpec& a, const TriggerSpec& b) {
-  return a.event_report_windows() == b.event_report_windows();
-}
-
 std::ostream& operator<<(std::ostream& out, const TriggerSpec& spec) {
   return out << spec.ToJson();
 }
 
-bool operator==(const TriggerSpecs& a, const TriggerSpecs& b) {
-  return base::ranges::equal(a, b);
-}
-
 std::ostream& operator<<(std::ostream& out, const TriggerSpecs& specs) {
   return out << specs.ToJson();
 }
diff --git a/components/attribution_reporting/test_utils.h b/components/attribution_reporting/test_utils.h
index 7ade629..8b1a97b 100644
--- a/components/attribution_reporting/test_utils.h
+++ b/components/attribution_reporting/test_utils.h
@@ -35,70 +35,36 @@
     mojom::SourceType,
     absl::optional<base::TimeDelta> lookback_window = absl::nullopt);
 
-bool operator==(const AggregationKeys&, const AggregationKeys&);
-
 std::ostream& operator<<(std::ostream&, const AggregationKeys&);
 
-bool operator==(const FilterData&, const FilterData&);
-
-bool operator==(const FilterConfig&, const FilterConfig&);
-
 std::ostream& operator<<(std::ostream&, const FilterData&);
 
-bool operator==(const FilterPair&, const FilterPair&);
-
 std::ostream& operator<<(std::ostream&, const FilterPair&);
 
-bool operator==(const DestinationSet&, const DestinationSet&);
-
 std::ostream& operator<<(std::ostream&, const DestinationSet&);
 
-bool operator==(const EventReportWindows&, const EventReportWindows&);
-
 std::ostream& operator<<(std::ostream&, const EventReportWindows&);
 
-bool operator==(const SourceRegistration&, const SourceRegistration&);
-
 std::ostream& operator<<(std::ostream&, const SourceRegistration&);
 
-bool operator==(const AggregatableValues&, const AggregatableValues&);
-
 std::ostream& operator<<(std::ostream&, const AggregatableValues&);
 
-bool operator==(const AggregatableTriggerData&, const AggregatableTriggerData&);
-
 std::ostream& operator<<(std::ostream&, const AggregatableTriggerData&);
 
-bool operator==(const EventTriggerData&, const EventTriggerData&);
-
 std::ostream& operator<<(std::ostream&, const EventTriggerData&);
 
-bool operator==(const TriggerRegistration&, const TriggerRegistration&);
-
 std::ostream& operator<<(std::ostream&, const TriggerRegistration&);
 
-bool operator==(const SuitableOrigin&, const SuitableOrigin&);
-
 std::ostream& operator<<(std::ostream&, const SuitableOrigin&);
 
-bool operator==(const AggregatableDedupKey&, const AggregatableDedupKey&);
-
 std::ostream& operator<<(std::ostream&, const AggregatableDedupKey&);
 
-bool operator==(const OsRegistrationItem&, const OsRegistrationItem&);
-
 std::ostream& operator<<(std::ostream&, const OsRegistrationItem&);
 
-bool operator==(const TriggerConfig&, const TriggerConfig&);
-
 std::ostream& operator<<(std::ostream&, const TriggerConfig&);
 
-bool operator==(const TriggerSpec&, const TriggerSpec&);
-
 std::ostream& operator<<(std::ostream&, const TriggerSpec&);
 
-bool operator==(const TriggerSpecs&, const TriggerSpecs&);
-
 std::ostream& operator<<(std::ostream&, const TriggerSpecs&);
 
 std::ostream& operator<<(std::ostream&, const TriggerSpecs::const_iterator&);
diff --git a/components/attribution_reporting/trigger_config.h b/components/attribution_reporting/trigger_config.h
index 41452c3..903f444 100644
--- a/components/attribution_reporting/trigger_config.h
+++ b/components/attribution_reporting/trigger_config.h
@@ -48,6 +48,8 @@
 
   base::Value::Dict ToJson() const;
 
+  friend bool operator==(const TriggerSpec&, const TriggerSpec&) = default;
+
  private:
   EventReportWindows event_report_windows_;
 };
@@ -178,6 +180,8 @@
 
   const std::vector<TriggerSpec>& specs() const { return specs_; }
 
+  friend bool operator==(const TriggerSpecs&, const TriggerSpecs&) = default;
+
  private:
   TriggerSpecs(TriggerDataIndices, std::vector<TriggerSpec>);
 
@@ -222,6 +226,8 @@
   // Always serializes regardless of the above feature status.
   void SerializeForTesting(base::Value::Dict&) const;
 
+  friend bool operator==(const TriggerConfig&, const TriggerConfig&) = default;
+
  private:
   mojom::TriggerDataMatching trigger_data_matching_ =
       mojom::TriggerDataMatching::kModulus;
diff --git a/components/attribution_reporting/trigger_registration.h b/components/attribution_reporting/trigger_registration.h
index 071c251..afa3a1b 100644
--- a/components/attribution_reporting/trigger_registration.h
+++ b/components/attribution_reporting/trigger_registration.h
@@ -59,6 +59,9 @@
 
   base::Value::Dict ToJson() const;
 
+  friend bool operator==(const TriggerRegistration&,
+                         const TriggerRegistration&) = default;
+
   FilterPair filters;
   absl::optional<uint64_t> debug_key;
   std::vector<AggregatableDedupKey> aggregatable_dedup_keys;
@@ -70,9 +73,6 @@
   attribution_reporting::mojom::SourceRegistrationTimeConfig
       source_registration_time_config =
           attribution_reporting::mojom::SourceRegistrationTimeConfig::kExclude;
-
-  // When adding new members, the corresponding `operator==()` definition in
-  // `test_utils.h` should also be updated.
 };
 
 }  // namespace attribution_reporting
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index 96266e6..16ab6a77 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -10,6 +10,7 @@
 #include <deque>
 #include <map>
 #include <memory>
+#include <optional>
 #include <sstream>
 #include <string>
 #include <unordered_map>
@@ -37,6 +38,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
+#include "base/types/optional_ref.h"
 #include "build/build_config.h"
 #include "components/autofill/core/browser/autofill_data_util.h"
 #include "components/autofill/core/browser/autofill_field.h"
@@ -739,49 +741,61 @@
   // Retrieves the next prediction for |form| and |field| and pops it. Popping
   // is omitted if no other predictions for |form| and |field| are left, so that
   // any subsequent fields with the same signature will get the same prediction.
+  std::set<FormSignature> signatures_seen;
   auto get_suggestion =
-      [&fields_suggestions](
-          FormSignature form,
-          FieldSignature field) -> std::optional<FieldSuggestion> {
-    auto it = fields_suggestions.find({form, field});
-    if (it == fields_suggestions.end()) {
+      [&fields_suggestions, &signatures_seen](
+          FormSignature form_signature,
+          FieldSignature field_signature) -> std::optional<FieldSuggestion> {
+    auto it = fields_suggestions.find({form_signature, field_signature});
+    if (it == fields_suggestions.end() ||
+        !signatures_seen.insert(form_signature).second) {
       return std::nullopt;
     }
-    DCHECK(!it->second.empty());
-    auto current_field = it->second.front();
+    CHECK(!it->second.empty());
+    FieldSuggestion current_field = it->second.front();
     if (it->second.size() > 1) {
       it->second.pop_front();
     }
-    return current_field;
+    return std::move(current_field);
   };
   // Precedence rule for prediction sources is the following:
   // Manual overrides first, then server overrides, then crowdsourcing of any
   // type. Moreover, Autofill deprioritizes any crowdsourcing that only returned
   // NO_SERVER_DATA. This is not done for overrides because overriding a field
   // as not classifiable could be desirable.
-  auto get_suggestion_priority = [](std::optional<FieldSuggestion> suggestion) {
-    if (!suggestion || suggestion->predictions().empty()) {
-      return 0;
-    }
-    switch (suggestion->predictions().begin()->source()) {
-      case FieldPrediction::SOURCE_UNSPECIFIED:
-      case FieldPrediction::SOURCE_AUTOFILL_DEFAULT:
-      case FieldPrediction::SOURCE_PASSWORDS_DEFAULT:
-      case FieldPrediction::SOURCE_ALL_APPROVED_EXPERIMENTS:
-      case FieldPrediction::SOURCE_FIELD_RANKS:
-        return base::ranges::any_of(suggestion->predictions(),
-                                    [](const auto& prediction) {
-                                      return prediction.type() !=
-                                             NO_SERVER_DATA;
-                                    })
-                   ? 1
-                   : 0;
-      case FieldPrediction::SOURCE_OVERRIDE:
-        return 2;
-      case FieldPrediction::SOURCE_MANUAL_OVERRIDE:
-        return 3;
-    }
-  };
+  auto get_suggestion_priority =
+      [](base::optional_ref<const FieldSuggestion> suggestion) {
+        if (!suggestion.has_value() || suggestion->predictions().empty()) {
+          return 0;  // Lowest priority
+        }
+        switch (suggestion->predictions().begin()->source()) {
+          case FieldPrediction::SOURCE_UNSPECIFIED:
+          case FieldPrediction::SOURCE_AUTOFILL_DEFAULT:
+          case FieldPrediction::SOURCE_PASSWORDS_DEFAULT:
+          case FieldPrediction::SOURCE_ALL_APPROVED_EXPERIMENTS:
+          case FieldPrediction::SOURCE_FIELD_RANKS:
+            return base::ranges::all_of(suggestion->predictions(),
+                                        [](const auto& prediction) {
+                                          return prediction.type() ==
+                                                 NO_SERVER_DATA;
+                                        })
+                       ? 1  // Only better than empty predictions.
+                       : 2;
+          case FieldPrediction::SOURCE_OVERRIDE:
+            return 3;
+          case FieldPrediction::SOURCE_MANUAL_OVERRIDE:
+            return 4;
+        }
+      };
+  // Fetch suggestions from form signature, host form signature and alternative
+  // form signature.
+  std::optional<FieldSuggestion> main_frame_field_suggestion =
+      get_suggestion(form.form_signature(), field.GetFieldSignature());
+  std::optional<FieldSuggestion> iframe_field_suggestion =
+      get_suggestion(field.host_form_signature, field.GetFieldSignature());
+  // NOTE: Suggestions from alternative form signatures are always overrides.
+  std::optional<FieldSuggestion> alternative_field_suggestion = get_suggestion(
+      form.alternative_form_signature(), field.GetFieldSignature());
 
   // Precedence rule for form signatures is the following:
   // `form_signature` (main frame) then `host_form_signature_` (iframe) and then
@@ -793,36 +807,26 @@
   // This precedence rule is less important than the source precedence rule,
   // which means that it is only applicable for suggestions with equal source
   // priority.
-  std::vector<FormSignature> form_signatures;
-  form_signatures.push_back(form.form_signature());
-  if (field.host_form_signature &&
-      field.host_form_signature != form.form_signature()) {
-    form_signatures.push_back(field.host_form_signature);
-  }
-  // NOTE: Suggestions from alternative form signatures are always overrides.
-  form_signatures.push_back(form.alternative_form_signature());
+  base::optional_ref<FieldSuggestion> preferred_field_suggestion =
+      base::ranges::max(
+          std::vector<base::optional_ref<FieldSuggestion>>{
+              main_frame_field_suggestion, iframe_field_suggestion,
+              alternative_field_suggestion},
+          {}, get_suggestion_priority);
 
-  std::optional<FieldSuggestion> field_suggestion;
-  for (FormSignature form_signature : form_signatures) {
-    std::optional<FieldSuggestion> candidate_suggestion =
-        get_suggestion(form_signature, field.GetFieldSignature());
-    // The strict > sign guarantees that for equal source precedence, we follow
-    // the signature precedence rule, since signatures are added to the list by
-    // order of precedence.
-    if (!field_suggestion || get_suggestion_priority(candidate_suggestion) >
-                                 get_suggestion_priority(field_suggestion)) {
-      field_suggestion = candidate_suggestion;
-    } else if (field_suggestion && candidate_suggestion &&
-               form_signature == field.host_form_signature &&
-               field.host_form_signature != form.form_signature() &&
-               !HasPasswordManagerPrediction(*field_suggestion) &&
-               HasPasswordManagerPrediction(*candidate_suggestion)) {
-      // Add predictions for PasswordManager from
-      // iframe suggestions if `field_suggestion` is missing them.
-      MergePasswordManagerPredictions(*candidate_suggestion, *field_suggestion);
-    }
+  // Add predictions for PasswordManager from `iframe_field_suggestions` if
+  // `field_suggestion` is missing them. This is only relevant for
+  // crowdsourcing which is why we do not apply the same logic for
+  // `alternative_form_signature` suggestions, which are always overrides.
+  if (iframe_field_suggestion &&
+      !HasPasswordManagerPrediction(*preferred_field_suggestion) &&
+      HasPasswordManagerPrediction(*iframe_field_suggestion)) {
+    MergePasswordManagerPredictions(*iframe_field_suggestion,
+                                    *preferred_field_suggestion);
   }
-  return field_suggestion;
+  return preferred_field_suggestion.has_value()
+             ? std::optional(std::move(*preferred_field_suggestion))
+             : std::nullopt;
 }
 
 // static
diff --git a/components/autofill/core/browser/logging/log_manager.h b/components/autofill/core/browser/logging/log_manager.h
index f7940ddc..d7795a06 100644
--- a/components/autofill/core/browser/logging/log_manager.h
+++ b/components/autofill/core/browser/logging/log_manager.h
@@ -5,9 +5,8 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_LOGGING_LOG_MANAGER_H_
 #define COMPONENTS_AUTOFILL_CORE_BROWSER_LOGGING_LOG_MANAGER_H_
 
+#include <concepts>
 #include <memory>
-#include <string>
-#include <type_traits>
 
 #include "base/functional/callback.h"
 #include "base/types/pass_key.h"
@@ -82,11 +81,8 @@
 namespace internal {
 
 // Traits for LOG_AF() macro for `LogManager*`.
-template <typename T>
-struct LoggerTraits<
-    T,
-    typename std::enable_if_t<std::is_convertible_v<decltype(std::declval<T>()),
-                                                    const LogManager*>>> {
+template <std::convertible_to<const LogManager*> T>
+struct LoggerTraits<T> {
   static bool active(const LogManager* log_manager) {
     return log_manager && log_manager->IsLoggingActive();
   }
@@ -97,11 +93,8 @@
 };
 
 // Traits for LOG_AF() macro for `LogManager&`.
-template <typename T>
-struct LoggerTraits<
-    T,
-    typename std::enable_if_t<std::is_convertible_v<decltype(std::declval<T>()),
-                                                    const LogManager&>>> {
+template <std::convertible_to<const LogManager&> T>
+struct LoggerTraits<T> {
   static bool active(const LogManager& log_manager) {
     return log_manager.IsLoggingActive();
   }
diff --git a/components/autofill/core/browser/webdata/autofill_change.h b/components/autofill/core/browser/webdata/autofill_change.h
index f7fd306..2bea2e6 100644
--- a/components/autofill/core/browser/webdata/autofill_change.h
+++ b/components/autofill/core/browser/webdata/autofill_change.h
@@ -64,8 +64,10 @@
       return;
     }
     if constexpr (std::is_same_v<DataType, Iban>) {
-      CHECK(data_model_.guid() == key_ ||
-            base::NumberToString(data_model_.instrument_id()) == key_);
+      CHECK((data_model_.record_type() == Iban::RecordType::kLocalIban &&
+             data_model_.guid() == key_) ||
+            (data_model_.record_type() == Iban::RecordType::kServerIban &&
+             base::NumberToString(data_model_.instrument_id()) == key_));
     } else if constexpr (std::is_same_v<DataType, ServerCvc>) {
       CHECK(base::NumberToString(data_model_.instrument_id) == key_);
     } else if constexpr (std::is_same_v<DataType, CreditCard>) {
diff --git a/components/autofill/core/browser/webdata/autofill_sync_bridge_test_util.cc b/components/autofill/core/browser/webdata/autofill_sync_bridge_test_util.cc
index 30cb24b2..51b20ad1 100644
--- a/components/autofill/core/browser/webdata/autofill_sync_bridge_test_util.cc
+++ b/components/autofill/core/browser/webdata/autofill_sync_bridge_test_util.cc
@@ -62,4 +62,21 @@
   return wallet_specifics;
 }
 
+sync_pb::AutofillWalletSpecifics CreateAutofillWalletSpecificsForIban(
+    const std::string& client_tag) {
+  sync_pb::AutofillWalletSpecifics wallet_specifics;
+  wallet_specifics.set_type(
+      sync_pb::AutofillWalletSpecifics_WalletInfoType::
+          AutofillWalletSpecifics_WalletInfoType_MASKED_IBAN);
+
+  sync_pb::WalletMaskedIban* iban_specifics =
+      wallet_specifics.mutable_masked_iban();
+  iban_specifics->set_instrument_id(client_tag);
+  iban_specifics->set_prefix("FR76");
+  iban_specifics->set_suffix("0189");
+  iban_specifics->set_length(27);
+  iban_specifics->set_nickname("My IBAN");
+  return wallet_specifics;
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/webdata/autofill_sync_bridge_test_util.h b/components/autofill/core/browser/webdata/autofill_sync_bridge_test_util.h
index c40dfff..2839c86 100644
--- a/components/autofill/core/browser/webdata/autofill_sync_bridge_test_util.h
+++ b/components/autofill/core/browser/webdata/autofill_sync_bridge_test_util.h
@@ -30,6 +30,9 @@
 CreateAutofillWalletSpecificsForCreditCardCloudTokenData(
     const std::string& client_tag);
 
+sync_pb::AutofillWalletSpecifics CreateAutofillWalletSpecificsForIban(
+    const std::string& client_tag);
+
 }  // namespace autofill
 
 #endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_SYNC_BRIDGE_TEST_UTIL_H_
diff --git a/components/autofill/core/browser/webdata/autofill_sync_bridge_util.cc b/components/autofill/core/browser/webdata/autofill_sync_bridge_util.cc
index 609e240..081a020 100644
--- a/components/autofill/core/browser/webdata/autofill_sync_bridge_util.cc
+++ b/components/autofill/core/browser/webdata/autofill_sync_bridge_util.cc
@@ -19,6 +19,7 @@
 #include "components/autofill/core/browser/data_model/autofill_wallet_usage_data.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/autofill/core/browser/data_model/credit_card_cloud_token_data.h"
+#include "components/autofill/core/browser/data_model/iban.h"
 #include "components/autofill/core/browser/payments/payments_customer_data.h"
 #include "components/autofill/core/browser/webdata/autofill_table.h"
 #include "components/autofill/core/common/autofill_util.h"
@@ -178,6 +179,18 @@
   return result;
 }
 
+// Creates an IBAN from the specified `iban` specifics.
+Iban IbanFromSpecifics(const sync_pb::WalletMaskedIban& iban) {
+  int64_t instrument_id = 0;
+  CHECK(base::StringToInt64(iban.instrument_id(), &instrument_id));
+  Iban result{Iban::InstrumentId(instrument_id)};
+  result.set_prefix(base::UTF8ToUTF16(iban.prefix()));
+  result.set_suffix(base::UTF8ToUTF16(iban.suffix()));
+  result.set_length(iban.length());
+  result.set_nickname(base::UTF8ToUTF16(iban.nickname()));
+  return result;
+}
+
 }  // namespace
 
 std::string GetBase64EncodedId(const std::string& id) {
@@ -338,6 +351,26 @@
       cloud_token_data.instrument_token);
 }
 
+void SetAutofillWalletSpecificsFromMaskedIban(
+    const Iban& iban,
+    sync_pb::AutofillWalletSpecifics* wallet_specifics,
+    bool enforce_utf8) {
+  wallet_specifics->set_type(AutofillWalletSpecifics::MASKED_IBAN);
+  sync_pb::WalletMaskedIban* wallet_iban =
+      wallet_specifics->mutable_masked_iban();
+  if (enforce_utf8) {
+    wallet_iban->set_instrument_id(
+        GetBase64EncodedId(base::NumberToString(iban.instrument_id())));
+  } else {
+    wallet_iban->set_instrument_id(base::NumberToString(iban.instrument_id()));
+  }
+
+  wallet_iban->set_prefix(base::UTF16ToUTF8(iban.prefix()));
+  wallet_iban->set_suffix(base::UTF16ToUTF8(iban.suffix()));
+  wallet_iban->set_nickname(base::UTF16ToUTF8(iban.nickname()));
+  wallet_iban->set_length(iban.length());
+}
+
 void SetAutofillWalletUsageSpecificsFromAutofillWalletUsageData(
     const AutofillWalletUsageData& wallet_usage_data,
     sync_pb::AutofillWalletUsageSpecifics* wallet_usage_specifics) {
@@ -551,6 +584,7 @@
 void PopulateWalletTypesFromSyncData(
     const syncer::EntityChangeList& entity_data,
     std::vector<CreditCard>* wallet_cards,
+    std::vector<Iban>& wallet_ibans,
     std::vector<PaymentsCustomerData>* customer_data,
     std::vector<CreditCardCloudTokenData>* cloud_token_data) {
   for (const std::unique_ptr<syncer::EntityChange>& change : entity_data) {
@@ -578,6 +612,10 @@
       case sync_pb::AutofillWalletSpecifics::PAYMENT_INSTRUMENT:
         // TODO(crbug.com/1472125) Support syncing of payment instruments.
         break;
+      case sync_pb::AutofillWalletSpecifics::MASKED_IBAN:
+        wallet_ibans.push_back(
+            IbanFromSpecifics(autofill_specifics.masked_iban()));
+        break;
       case sync_pb::AutofillWalletSpecifics::UNKNOWN:
         // Just ignore new entry types that the client doesn't know about.
         break;
diff --git a/components/autofill/core/browser/webdata/autofill_sync_bridge_util.h b/components/autofill/core/browser/webdata/autofill_sync_bridge_util.h
index 07556977..516ec97 100644
--- a/components/autofill/core/browser/webdata/autofill_sync_bridge_util.h
+++ b/components/autofill/core/browser/webdata/autofill_sync_bridge_util.h
@@ -19,6 +19,7 @@
 class AutofillTable;
 class CreditCard;
 struct CreditCardCloudTokenData;
+class Iban;
 struct PaymentsCustomerData;
 class VirtualCardUsageData;
 
@@ -52,6 +53,13 @@
     sync_pb::AutofillWalletSpecifics* wallet_specifics,
     bool enforce_utf8 = false);
 
+// Sets the field of the `wallet_specifics` based on the specified
+// `iban`. If `enforce_utf8`, ids are encoded into UTF-8.
+void SetAutofillWalletSpecificsFromMaskedIban(
+    const Iban& iban,
+    sync_pb::AutofillWalletSpecifics* wallet_specifics,
+    bool enforce_utf8 = false);
+
 // Sets the field of the `wallet_usage_specifics` based on the specified
 // `wallet_usage_data`.
 void SetAutofillWalletUsageSpecificsFromAutofillWalletUsageData(
@@ -98,9 +106,11 @@
     std::vector<CreditCard>* cards_from_server);
 
 // Populates the wallet datatypes from the sync data.
+// TODO(crbug.com/1500315): Refactor to take parameters as reference.
 void PopulateWalletTypesFromSyncData(
     const ::syncer::EntityChangeList& entity_data,
     std::vector<CreditCard>* wallet_cards,
+    std::vector<Iban>& wallet_ibans,
     std::vector<PaymentsCustomerData>* customer_data,
     std::vector<CreditCardCloudTokenData>* cloud_token_data);
 
diff --git a/components/autofill/core/browser/webdata/autofill_sync_bridge_util_unittest.cc b/components/autofill/core/browser/webdata/autofill_sync_bridge_util_unittest.cc
index 29bb52f..21b24618 100644
--- a/components/autofill/core/browser/webdata/autofill_sync_bridge_util_unittest.cc
+++ b/components/autofill/core/browser/webdata/autofill_sync_bridge_util_unittest.cc
@@ -81,6 +81,8 @@
   // Add two credit cards.
   std::string credit_card_id_1 = "credit_card_1";
   std::string credit_card_id_2 = "credit_card_2";
+  // Add one IBAN.
+  std::string iban_id = "12345678";
   // Add the first card that has its billing address id set to the address's id.
   // No nickname is set.
   sync_pb::AutofillWalletSpecifics wallet_specifics_card1 =
@@ -116,12 +118,18 @@
       "https://www.example.com/card.png");
   wallet_specifics_card2.mutable_masked_card()->set_product_description(
       "fake product description");
+  sync_pb::AutofillWalletSpecifics wallet_specifics_iban =
+      CreateAutofillWalletSpecificsForIban(
+          /*client_tag=*/iban_id);
   entity_data.push_back(EntityChange::CreateAdd(
       credit_card_id_1,
       SpecificsToEntity(wallet_specifics_card1, /*client_tag=*/"card-card1")));
   entity_data.push_back(EntityChange::CreateAdd(
       credit_card_id_2,
       SpecificsToEntity(wallet_specifics_card2, /*client_tag=*/"card-card2")));
+  entity_data.push_back(EntityChange::CreateAdd(
+      iban_id,
+      SpecificsToEntity(wallet_specifics_iban, /*client_tag=*/"iban")));
   // Add payments customer data.
   entity_data.push_back(EntityChange::CreateAdd(
       "deadbeef",
@@ -136,10 +144,11 @@
                    /*client_tag=*/"token-data1")));
 
   std::vector<CreditCard> wallet_cards;
+  std::vector<Iban> wallet_ibans;
   std::vector<PaymentsCustomerData> customer_data;
   std::vector<CreditCardCloudTokenData> cloud_token_data;
-  PopulateWalletTypesFromSyncData(entity_data, &wallet_cards, &customer_data,
-                                  &cloud_token_data);
+  PopulateWalletTypesFromSyncData(entity_data, &wallet_cards, wallet_ibans,
+                                  &customer_data, &cloud_token_data);
 
   ASSERT_EQ(2U, wallet_cards.size());
 
diff --git a/components/autofill/core/browser/webdata/autofill_table.cc b/components/autofill/core/browser/webdata/autofill_table.cc
index 8123e9e..e3a2c93 100644
--- a/components/autofill/core/browser/webdata/autofill_table.cc
+++ b/components/autofill/core/browser/webdata/autofill_table.cc
@@ -2401,19 +2401,21 @@
   return s.Succeeded();
 }
 
-bool AutofillTable::AddOrUpdateServerIbanMetadata(const Iban& iban) {
-  CHECK_EQ(Iban::RecordType::kServerIban, iban.record_type());
+bool AutofillTable::AddOrUpdateServerIbanMetadata(
+    const AutofillMetadata& iban_metadata) {
   // There's no need to verify if removal succeeded, because if it's a new IBAN,
   // the removal call won't do anything.
-  RemoveServerIbanMetadata(base::NumberToString(iban.instrument_id()));
+  RemoveServerIbanMetadata(iban_metadata.id);
 
   sql::Statement s;
   InsertBuilder(db_, s, kMaskedIbansMetadataTable,
                 {kInstrumentId, kUseCount, kUseDate});
-  s.BindString(0, iban.GetMetadata().id);
-  s.BindInt64(1, iban.GetMetadata().use_count);
-  s.BindTime(2, iban.GetMetadata().use_date);
-  return s.Run();
+  s.BindString(0, iban_metadata.id);
+  s.BindInt64(1, iban_metadata.use_count);
+  s.BindTime(2, iban_metadata.use_date);
+  s.Run();
+
+  return db_->GetLastChangeCount() > 0;
 }
 
 bool AutofillTable::RemoveServerIbanMetadata(const std::string& instrument_id) {
@@ -2543,14 +2545,14 @@
   return s.Succeeded();
 }
 
-std::vector<std::unique_ptr<Iban>> AutofillTable::GetServerIbans() {
+bool AutofillTable::GetServerIbans(std::vector<std::unique_ptr<Iban>>& ibans) {
   sql::Statement s;
   SelectBuilder(db_, s, kMaskedIbansTable,
                 {kInstrumentId, kUseCount, kUseDate, kNickname, kPrefix,
                  kSuffix, kLength},
                 "LEFT OUTER JOIN masked_ibans_metadata USING (instrument_id)");
 
-  std::vector<std::unique_ptr<Iban>> ibans;
+  ibans.clear();
   while (s.Step()) {
     int index = 0;
     int64_t instrument_id = 0;
@@ -2568,7 +2570,7 @@
     ibans.push_back(std::move(iban));
   }
 
-  return ibans;
+  return s.Succeeded();
 }
 
 bool AutofillTable::SetServerIbans(const std::vector<Iban>& ibans) {
@@ -2597,7 +2599,7 @@
     s.Reset(/*clear_bound_vars=*/true);
 
     // Save the use count and use date of the IBAN.
-    AddOrUpdateServerIbanMetadata(iban);
+    AddOrUpdateServerIbanMetadata(iban.GetMetadata());
   }
   return transaction.Commit();
 }
diff --git a/components/autofill/core/browser/webdata/autofill_table.h b/components/autofill/core/browser/webdata/autofill_table.h
index 8e48ce1..dd06eff 100644
--- a/components/autofill/core/browser/webdata/autofill_table.h
+++ b/components/autofill/core/browser/webdata/autofill_table.h
@@ -806,7 +806,7 @@
   bool RemoveServerCardMetadata(const std::string& id);
   bool GetServerCardsMetadata(
       std::map<std::string, AutofillMetadata>* cards_metadata) const;
-  bool AddOrUpdateServerIbanMetadata(const Iban& iban);
+  bool AddOrUpdateServerIbanMetadata(const AutofillMetadata& iban_metadata);
   bool RemoveServerIbanMetadata(const std::string& instrument_id);
   std::vector<AutofillMetadata> GetServerIbansMetadata() const;
 
@@ -821,8 +821,9 @@
       std::vector<std::unique_ptr<CreditCardCloudTokenData>>*
           credit_card_cloud_token_data);
 
-  // Gets the list of server IBANs from the database.
-  std::vector<std::unique_ptr<Iban>> GetServerIbans();
+  // Returns true if server IBANs are successfully returned via `ibans` from
+  // the database.
+  bool GetServerIbans(std::vector<std::unique_ptr<Iban>>& ibans);
   // Overwrite the IBANs in the database with the given `ibans`.
   bool SetServerIbans(const std::vector<Iban>& ibans);
 
diff --git a/components/autofill/core/browser/webdata/autofill_table_unittest.cc b/components/autofill/core/browser/webdata/autofill_table_unittest.cc
index 891fbc6..f2fb001 100644
--- a/components/autofill/core/browser/webdata/autofill_table_unittest.cc
+++ b/components/autofill/core/browser/webdata/autofill_table_unittest.cc
@@ -1160,8 +1160,8 @@
 
   EXPECT_TRUE(table_->SetServerIbans(ibans));
 
-  std::vector<std::unique_ptr<Iban>> masked_server_ibans =
-      table_->GetServerIbans();
+  std::vector<std::unique_ptr<Iban>> masked_server_ibans;
+  EXPECT_TRUE(table_->GetServerIbans(masked_server_ibans));
   EXPECT_EQ(3U, masked_server_ibans.size());
   EXPECT_THAT(ibans, UnorderedElementsAre(*masked_server_ibans[0],
                                           *masked_server_ibans[1],
@@ -2220,7 +2220,7 @@
   // Set the metadata.
   iban.set_use_count(50);
   iban.set_use_date(AutofillClock::Now());
-  EXPECT_TRUE(table_->AddOrUpdateServerIbanMetadata(iban));
+  EXPECT_TRUE(table_->AddOrUpdateServerIbanMetadata(iban.GetMetadata()));
 
   // Make sure it was added correctly.
   std::vector<AutofillMetadata> outputs = table_->GetServerIbansMetadata();
@@ -2305,14 +2305,15 @@
   std::vector<Iban> inputs = {test::GetServerIban()};
   table_->SetServerIbans(inputs);
 
-  std::vector<std::unique_ptr<Iban>> outputs = table_->GetServerIbans();
+  std::vector<std::unique_ptr<Iban>> outputs;
+  EXPECT_TRUE(table_->GetServerIbans(outputs));
   ASSERT_EQ(1U, outputs.size());
   EXPECT_EQ(inputs[0].instrument_id(), outputs[0]->instrument_id());
 
   // Update metadata in the IBAN.
   outputs[0]->set_use_count(outputs[0]->use_count() + 1);
 
-  EXPECT_TRUE(table_->AddOrUpdateServerIbanMetadata(*outputs[0]));
+  EXPECT_TRUE(table_->AddOrUpdateServerIbanMetadata(outputs[0]->GetMetadata()));
 
   // Make sure it was updated correctly.
   std::vector<AutofillMetadata> output_metadata =
@@ -2321,7 +2322,8 @@
   EXPECT_EQ(outputs[0]->GetMetadata(), output_metadata[0]);
 
   // Make sure nothing else got updated.
-  std::vector<std::unique_ptr<Iban>> outputs2 = table_->GetServerIbans();
+  std::vector<std::unique_ptr<Iban>> outputs2;
+  EXPECT_TRUE(table_->GetServerIbans(outputs2));
   ASSERT_EQ(1U, outputs2.size());
   EXPECT_EQ(0, outputs[0]->Compare(*outputs2[0]));
 }
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.cc b/components/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.cc
index 4a44c81..9b022a3 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.cc
@@ -52,6 +52,7 @@
       return "address-" + specifics_id;
     case WalletMetadataSpecifics::CARD:
       return "card-" + specifics_id;
+    case WalletMetadataSpecifics::IBAN:
     case WalletMetadataSpecifics::UNKNOWN:
       NOTREACHED();
       return "";
@@ -234,6 +235,7 @@
   switch (type) {
     case WalletMetadataSpecifics::CARD:
       return table->AddServerCardMetadata(metadata);
+    case WalletMetadataSpecifics::IBAN:
     // ADDRESS metadata syncing is deprecated.
     case WalletMetadataSpecifics::ADDRESS:
     case WalletMetadataSpecifics::UNKNOWN:
@@ -248,6 +250,7 @@
   switch (type) {
     case WalletMetadataSpecifics::CARD:
       return table->RemoveServerCardMetadata(id);
+    case WalletMetadataSpecifics::IBAN:
     // ADDRESS metadata syncing is deprecated.
     case WalletMetadataSpecifics::ADDRESS:
     case WalletMetadataSpecifics::UNKNOWN:
@@ -262,6 +265,7 @@
   switch (type) {
     case WalletMetadataSpecifics::CARD:
       return table->UpdateServerCardMetadata(metadata);
+    case WalletMetadataSpecifics::IBAN:
     // ADDRESS metadata syncing is deprecated.
     case WalletMetadataSpecifics::ADDRESS:
     case WalletMetadataSpecifics::UNKNOWN:
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.cc b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.cc
index 646390b..6b521b3 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.cc
@@ -6,12 +6,14 @@
 
 #include <utility>
 
+#include "base/containers/contains.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/logging.h"
 #include "base/ranges/algorithm.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/autofill/core/browser/data_model/credit_card_cloud_token_data.h"
+#include "components/autofill/core/browser/data_model/iban.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
 #include "components/autofill/core/browser/payments/payments_customer_data.h"
 #include "components/autofill/core/browser/webdata/autofill_sync_bridge_util.h"
@@ -50,6 +52,10 @@
   return cloud_token_data.instrument_token;
 }
 
+std::string GetClientTagFromIban(const Iban& iban) {
+  return base::NumberToString(iban.instrument_id());
+}
+
 // Returns the storage key to be used for wallet data for the specified wallet
 // data |client_tag|.
 std::string GetStorageKeyForWalletDataClientTag(const std::string& client_tag) {
@@ -108,6 +114,20 @@
   return entity_data;
 }
 
+// Creates a EntityData object corresponding to the specified `iban`.
+std::unique_ptr<EntityData> CreateEntityDataFromIban(const Iban& iban,
+                                                     bool enforce_utf8) {
+  auto entity_data = std::make_unique<EntityData>();
+  entity_data->name =
+      "Server IBAN " + GetBase64EncodedId(GetClientTagFromIban(iban));
+
+  AutofillWalletSpecifics* wallet_specifics =
+      entity_data->specifics.mutable_autofill_wallet();
+  SetAutofillWalletSpecificsFromMaskedIban(iban, wallet_specifics,
+                                           enforce_utf8);
+  return entity_data;
+}
+
 }  // namespace
 
 // static
@@ -230,9 +250,11 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   std::vector<std::unique_ptr<CreditCard>> cards;
+  std::vector<std::unique_ptr<Iban>> ibans;
   std::vector<std::unique_ptr<CreditCardCloudTokenData>> cloud_token_data;
   std::unique_ptr<PaymentsCustomerData> customer_data;
   if (!GetAutofillTable()->GetServerCreditCards(&cards) ||
+      !GetAutofillTable()->GetServerIbans(ibans) ||
       !GetAutofillTable()->GetCreditCardCloudTokenData(&cloud_token_data) ||
       !GetAutofillTable()->GetPaymentsCustomerData(&customer_data)) {
     change_processor()->ReportError(
@@ -259,6 +281,11 @@
                    GetClientTagFromPaymentsCustomerData(*customer_data)),
                CreateEntityDataFromPaymentsCustomerData(*customer_data));
   }
+  for (const std::unique_ptr<Iban>& entry : ibans) {
+    batch->Put(
+        GetStorageKeyForWalletDataClientTag(GetClientTagFromIban(*entry)),
+        CreateEntityDataFromIban(*entry, enforce_utf8));
+  }
   std::move(callback).Run(std::move(batch));
 }
 
@@ -269,13 +296,16 @@
 
   // Extract the Autofill types from the sync |entity_data|.
   std::vector<CreditCard> wallet_cards;
+  std::vector<Iban> wallet_ibans;
   std::vector<PaymentsCustomerData> customer_data;
   std::vector<CreditCardCloudTokenData> cloud_token_data;
-  PopulateWalletTypesFromSyncData(entity_data, &wallet_cards, &customer_data,
-                                  &cloud_token_data);
+  PopulateWalletTypesFromSyncData(entity_data, &wallet_cards, wallet_ibans,
+                                  &customer_data, &cloud_token_data);
 
   wallet_data_changed |=
       SetWalletCards(std::move(wallet_cards), notify_webdata_backend);
+  wallet_data_changed |=
+      SetWalletIbans(std::move(wallet_ibans), notify_webdata_backend);
   wallet_data_changed |= SetPaymentsCustomerData(std::move(customer_data));
   wallet_data_changed |=
       SetCreditCardCloudTokenData(std::move(cloud_token_data));
@@ -330,6 +360,46 @@
   return false;
 }
 
+bool AutofillWalletSyncBridge::SetWalletIbans(std::vector<Iban> wallet_ibans,
+                                              bool notify_webdata_backend) {
+  AutofillTable* table = GetAutofillTable();
+
+  std::vector<std::unique_ptr<Iban>> existing_ibans;
+  if (!table->GetServerIbans(existing_ibans)) {
+    return false;
+  }
+
+  GetAutofillTable()->SetServerIbans(wallet_ibans);
+  bool found_diff = false;
+  if (notify_webdata_backend) {
+    for (const std::unique_ptr<Iban>& existing_iban : existing_ibans) {
+      bool has_orphan_iban = base::ranges::none_of(
+          wallet_ibans,
+          [&](const Iban& iban) { return iban.Compare(*existing_iban) == 0; });
+      if (has_orphan_iban) {
+        web_data_backend_->NotifyOfIbanChanged(
+            IbanChange(IbanChange::REMOVE,
+                       base::NumberToString(existing_iban->instrument_id()),
+                       *existing_iban));
+        found_diff = true;
+      }
+    }
+    for (const Iban& wallet_iban : wallet_ibans) {
+      bool has_new_iban = base::ranges::none_of(
+          existing_ibans, [&](const std::unique_ptr<Iban>& iban) {
+            return iban->Compare(wallet_iban) == 0;
+          });
+      if (has_new_iban) {
+        web_data_backend_->NotifyOfIbanChanged(IbanChange(
+            IbanChange::ADD, base::NumberToString(wallet_iban.instrument_id()),
+            wallet_iban));
+        found_diff = true;
+      }
+    }
+  }
+  return found_diff;
+}
+
 bool AutofillWalletSyncBridge::SetPaymentsCustomerData(
     std::vector<PaymentsCustomerData> customer_data) {
   AutofillTable* table = GetAutofillTable();
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.h b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.h
index e48a3cb..b520db8 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.h
+++ b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge.h
@@ -24,6 +24,7 @@
 class AutofillWebDataBackend;
 class AutofillWebDataService;
 class CreditCard;
+class Iban;
 struct CreditCardCloudTokenData;
 struct PaymentsCustomerData;
 
@@ -105,6 +106,13 @@
   bool SetWalletCards(std::vector<CreditCard> wallet_cards,
                       bool notify_webdata_backend);
 
+  // Sets `wallet_ibans` to this client and returns whether any change has been
+  // applied (i.e., whether `wallet_ibans` was different from local data). If
+  // `notify_webdata_backend` is true, it also notifies via WebDataBackend about
+  // any individual entity changes.
+  bool SetWalletIbans(std::vector<Iban> wallet_ibans,
+                      bool notify_webdata_backend);
+
   // Sets |cloud_token_data| to this client and returns whether any change has
   // been applied (i.e., whether |cloud_token_data| was different from the local
   // data).
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge_unittest.cc b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge_unittest.cc
index fb07c32..2adfcac 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge_unittest.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_sync_bridge_unittest.cc
@@ -165,6 +165,17 @@
   return output.str();
 }
 
+std::string WalletMaskedIbanSpecificsAsDebugString(
+    const AutofillWalletSpecifics& specifics) {
+  std::ostringstream output;
+  output << "[id: " << specifics.masked_iban().instrument_id()
+         << ", prefix: " << specifics.masked_iban().prefix()
+         << ", suffix: " << specifics.masked_iban().suffix()
+         << ", length: " << specifics.masked_iban().length()
+         << ", nickname: " << specifics.masked_iban().nickname() << "]";
+  return output.str();
+}
+
 std::string AutofillWalletSpecificsAsDebugString(
     const AutofillWalletSpecifics& specifics) {
   switch (specifics.type()) {
@@ -184,6 +195,9 @@
         AutofillWalletSpecifics_WalletInfoType_PAYMENT_INSTRUMENT:
       return WalletPaymentInstrumentAsDebugString(specifics);
     case sync_pb::AutofillWalletSpecifics_WalletInfoType::
+        AutofillWalletSpecifics_WalletInfoType_MASKED_IBAN:
+      return WalletMaskedIbanSpecificsAsDebugString(specifics);
+    case sync_pb::AutofillWalletSpecifics_WalletInfoType::
         AutofillWalletSpecifics_WalletInfoType_UNKNOWN:
       return "Unknown";
   }
@@ -873,6 +887,59 @@
             cloud_token_data_vector[0]->instrument_token);
 }
 
+// Tests that all field values for an IBAN sent from the server are copied on
+// the client.
+TEST_F(AutofillWalletSyncBridgeTest, MergeFullSyncData_SetsNewMaskedIban) {
+  Iban server_iban = test::GetServerIban();
+
+  AutofillWalletSpecifics masked_iban_specifics;
+  SetAutofillWalletSpecificsFromMaskedIban(server_iban, &masked_iban_specifics);
+
+  EXPECT_CALL(*backend(),
+              NotifyOnAutofillChangedBySync(syncer::AUTOFILL_WALLET_DATA));
+  EXPECT_CALL(*backend(), NotifyOfIbanChanged(AddChange(
+                              base::NumberToString(server_iban.instrument_id()),
+                              server_iban)));
+  StartSyncing({masked_iban_specifics});
+
+  EXPECT_THAT(GetAllLocalData(),
+              UnorderedElementsAre(EqualsSpecifics(masked_iban_specifics)));
+  std::vector<std::unique_ptr<Iban>> iban_vector;
+  ASSERT_TRUE(table()->GetServerIbans(iban_vector));
+  EXPECT_THAT(iban_vector, UnorderedElementsAre(testing::Pointee(server_iban)));
+}
+
+// Tests that when there are existing IBANs, the data from the server is what
+// the client ends up with.
+TEST_F(AutofillWalletSyncBridgeTest,
+       MergeFullSyncData_UpdateAndRemoveMaskedIban) {
+  Iban server_iban1 = test::GetServerIban();
+  table()->SetServerIbans({server_iban1});
+
+  // Create a `server_iban2` which has the same data as `server_iban1` but with
+  // a different nickname.
+  Iban server_iban2 = server_iban1;
+  server_iban2.set_nickname(u"Another nickname");
+  AutofillWalletSpecifics masked_iban2_specifics;
+  SetAutofillWalletSpecificsFromMaskedIban(server_iban2,
+                                           &masked_iban2_specifics);
+
+  EXPECT_CALL(*backend(),
+              NotifyOnAutofillChangedBySync(syncer::AUTOFILL_WALLET_DATA));
+  EXPECT_CALL(*backend(), NotifyOfIbanChanged(RemoveChange(base::NumberToString(
+                              server_iban1.instrument_id()))));
+  EXPECT_CALL(
+      *backend(),
+      NotifyOfIbanChanged(AddChange(
+          base::NumberToString(server_iban2.instrument_id()), server_iban2)));
+  StartSyncing({masked_iban2_specifics});
+
+  std::vector<std::unique_ptr<Iban>> iban_vector;
+  ASSERT_TRUE(table()->GetServerIbans(iban_vector));
+  EXPECT_THAT(iban_vector,
+              UnorderedElementsAre(testing::Pointee(server_iban2)));
+}
+
 TEST_F(AutofillWalletSyncBridgeTest, LoadMetadataCalled) {
   EXPECT_TRUE(table()->UpdateEntityMetadata(syncer::AUTOFILL_WALLET_DATA, "key",
                                             EntityMetadata()));
diff --git a/components/autofill/core/browser/webdata/autofill_webdata_backend.h b/components/autofill/core/browser/webdata/autofill_webdata_backend.h
index fc8553b..6432f6c 100644
--- a/components/autofill/core/browser/webdata/autofill_webdata_backend.h
+++ b/components/autofill/core/browser/webdata/autofill_webdata_backend.h
@@ -50,6 +50,12 @@
   // sequence notifications are asynchronous.
   virtual void NotifyOfCreditCardChanged(const CreditCardChange& change) = 0;
 
+  // Notifies listeners on the DB sequence that an IBAN has been
+  // added/removed/updated in the WebDatabase.
+  // NOTE: This method is intended to be called from the DB sequence. The UI
+  // sequence notifications are asynchronous.
+  virtual void NotifyOfIbanChanged(const IbanChange& change) = 0;
+
   // Notifies listeners on UI sequences that changes have been made to
   // Autofill records of the database by the sync.
   // NOTE: The UI sequence notifications are asynchronous.
diff --git a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc
index 95d2ba52..02285a3 100644
--- a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc
+++ b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc
@@ -206,6 +206,16 @@
     db_observer.CreditCardChanged(change);
 }
 
+void AutofillWebDataBackendImpl::NotifyOfIbanChanged(const IbanChange& change) {
+  DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
+
+  // DB sequence notification.
+  for (AutofillWebDataServiceObserverOnDBSequence& db_observer :
+       db_observer_list_) {
+    db_observer.IbanChanged(change);
+  }
+}
+
 void AutofillWebDataBackendImpl::NotifyOnAutofillChangedBySync(
     syncer::ModelType model_type) {
   DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
@@ -626,8 +636,8 @@
 std::unique_ptr<WDTypedResult> AutofillWebDataBackendImpl::GetServerIbans(
     WebDatabase* db) {
   DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
-  std::vector<std::unique_ptr<Iban>> ibans =
-      AutofillTable::FromWebDatabase(db)->GetServerIbans();
+  std::vector<std::unique_ptr<Iban>> ibans;
+  AutofillTable::FromWebDatabase(db)->GetServerIbans(ibans);
   return std::make_unique<WDResult<std::vector<std::unique_ptr<Iban>>>>(
       AUTOFILL_IBANS_RESULT, std::move(ibans));
 }
diff --git a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h
index 4ccb4fb..d5a305a 100644
--- a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h
+++ b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h
@@ -72,6 +72,7 @@
   void NotifyOfAutofillProfileChanged(
       const AutofillProfileChange& change) override;
   void NotifyOfCreditCardChanged(const CreditCardChange& change) override;
+  void NotifyOfIbanChanged(const IbanChange& change) override;
   void NotifyOnAutofillChangedBySync(syncer::ModelType model_type) override;
   void CommitChanges() override;
 
diff --git a/components/autofill/core/browser/webdata/mock_autofill_webdata_backend.h b/components/autofill/core/browser/webdata/mock_autofill_webdata_backend.h
index 3b8e77e6..6c6b23f 100644
--- a/components/autofill/core/browser/webdata/mock_autofill_webdata_backend.h
+++ b/components/autofill/core/browser/webdata/mock_autofill_webdata_backend.h
@@ -45,6 +45,10 @@
               (const CreditCardChange& change),
               (override));
   MOCK_METHOD(void,
+              NotifyOfIbanChanged,
+              (const IbanChange& change),
+              (override));
+  MOCK_METHOD(void,
               NotifyOnAutofillChangedBySync,
               (syncer::ModelType model_type),
               (override));
diff --git a/components/autofill/core/common/autofill_payments_features.cc b/components/autofill/core/common/autofill_payments_features.cc
index c5b94c75..ccc548f 100644
--- a/components/autofill/core/common/autofill_payments_features.cc
+++ b/components/autofill/core/common/autofill_payments_features.cc
@@ -32,7 +32,11 @@
 // Payments Autofill UI.
 BASE_FEATURE(kAutofillEnableCardArtImage,
              "AutofillEnableCardArtImage",
+#if BUILDFLAG(IS_IOS)
+             base::FEATURE_ENABLED_BY_DEFAULT);
+#else
              base::FEATURE_DISABLED_BY_DEFAULT);
+#endif
 
 // When enabled, server will return card art images of the exact required
 // dimension.
@@ -44,7 +48,11 @@
 // Payments Autofill UI.
 BASE_FEATURE(kAutofillEnableCardProductName,
              "AutofillEnableCardProductName",
+#if BUILDFLAG(IS_IOS)
+             base::FEATURE_ENABLED_BY_DEFAULT);
+#else
              base::FEATURE_DISABLED_BY_DEFAULT);
+#endif
 
 // When enabled, we will store CVC for both local and server credit cards. This
 // will also allow the users to autofill their CVCs on checkout pages.
@@ -178,11 +186,10 @@
 BASE_FEATURE(kAutofillEnableUpdateVirtualCardEnrollment,
              "AutofillEnableUpdateVirtualCardEnrollment",
 #if BUILDFLAG(IS_IOS)
-             base::FEATURE_DISABLED_BY_DEFAULT
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #else
-             base::FEATURE_ENABLED_BY_DEFAULT
+             base::FEATURE_ENABLED_BY_DEFAULT);
 #endif
-);
 
 // When enabled, the vcn enroll screen will present a loading spinner while
 // enrolling the card to the server and present a confirmation screen with the
@@ -250,7 +257,7 @@
 // card number. (E.g., '•• 8888' rather than '•••• 8888').
 BASE_FEATURE(kAutofillUseTwoDotsForLastFourDigits,
              "AutofillUseTwoDotsForLastFourDigits",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // When this and the above `kAutofillEnablePaymentsMandatoryReauth` are both
 // enabled, in use-cases where we would not have triggered any user-visible
diff --git a/components/autofill/core/common/logging/log_buffer.h b/components/autofill/core/common/logging/log_buffer.h
index 3c77e2c5..9b0fa16 100644
--- a/components/autofill/core/common/logging/log_buffer.h
+++ b/components/autofill/core/common/logging/log_buffer.h
@@ -5,8 +5,8 @@
 #ifndef COMPONENTS_AUTOFILL_CORE_COMMON_LOGGING_LOG_BUFFER_H_
 #define COMPONENTS_AUTOFILL_CORE_COMMON_LOGGING_LOG_BUFFER_H_
 
+#include <concepts>
 #include <string>
-#include <type_traits>
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
@@ -140,8 +140,8 @@
 };
 
 // Enable streaming numbers of all types.
-template <typename T,
-          typename = std::enable_if_t<std::is_arithmetic<T>::value, T>>
+template <typename T>
+  requires(std::integral<T> || std::floating_point<T>)
 LogBuffer& operator<<(LogBuffer& buf, T number) {
   return buf << base::NumberToString(number);
 }
@@ -228,11 +228,8 @@
 namespace internal {
 
 // Traits for LOG_AF() macro for `LogBuffer*`.
-template <typename T>
-struct LoggerTraits<
-    T,
-    typename std::enable_if_t<
-        std::is_convertible_v<decltype(std::declval<T>()), const LogBuffer*>>> {
+template <std::convertible_to<const LogBuffer*> T>
+struct LoggerTraits<T> {
   static bool active(const LogBuffer* log_buffer) {
     return log_buffer && log_buffer->active();
   }
@@ -241,11 +238,8 @@
 };
 
 // Traits for LOG_AF() macro for `LogBuffer&`.
-template <typename T>
-struct LoggerTraits<
-    T,
-    typename std::enable_if_t<
-        std::is_convertible_v<decltype(std::declval<T>()), const LogBuffer&>>> {
+template <std::convertible_to<const LogBuffer&> T>
+struct LoggerTraits<T> {
   static bool active(const LogBuffer& log_buffer) {
     return log_buffer.active();
   }
diff --git a/components/autofill/core/common/logging/log_macros.h b/components/autofill/core/common/logging/log_macros.h
index 4bdff9c3..92bd38a2 100644
--- a/components/autofill/core/common/logging/log_macros.h
+++ b/components/autofill/core/common/logging/log_macros.h
@@ -25,7 +25,7 @@
 
 // Traits for targets of LOG_AF(). There are currently specializations for
 // `LogManager*` and `LogBuffer*`.
-template <typename T, typename Enable = void>
+template <typename T>
 struct LoggerTraits {
   // Returns true iff logging to should be enabled.
   static bool active(const T& logger) { return false; }
diff --git a/components/autofill/ios/form_util/resources/fill_element_inference.ts b/components/autofill/ios/form_util/resources/fill_element_inference.ts
index 2d13ce3..b3c665ef 100644
--- a/components/autofill/ios/form_util/resources/fill_element_inference.ts
+++ b/components/autofill/ios/form_util/resources/fill_element_inference.ts
@@ -481,7 +481,7 @@
           !(node as HTMLLabelElement).control) {
         inferredLabel = inferenceUtil.findChildText(node);
       } else if (node.nodeType === Node.TEXT_NODE) {
-        inferredLabel = gCrWeb.fill.nodeValue(node).trim();
+        inferredLabel = inferenceUtil.nodeValue(node).trim();
       }
     } else if (inferenceUtil.isTraversableContainerElement(node)) {
       // If the element is in a non-div container, its label most likely is too.
diff --git a/components/autofill/ios/form_util/resources/fill_element_inference_util.ts b/components/autofill/ios/form_util/resources/fill_element_inference_util.ts
index 55cb994..2153b1b 100644
--- a/components/autofill/ios/form_util/resources/fill_element_inference_util.ts
+++ b/components/autofill/ios/form_util/resources/fill_element_inference_util.ts
@@ -149,7 +149,7 @@
   // Extract the text exactly at this node.
   let nodeText = '';
   if (!skipNode) {
-    nodeText = gCrWeb.fill.nodeValue(node);
+    nodeText = nodeValue(node);
     if (node.nodeType === Node.TEXT_NODE && !nodeText) {
       // In the C++ version, this text node would have been stripped completely.
       // Just pass the buck.
@@ -197,7 +197,7 @@
  */
 function findChildTextWithIgnoreList(node: Node, divsToSkip: Node[]): string {
   if (node.nodeType === Node.TEXT_NODE) {
-    return gCrWeb.fill.nodeValue(node);
+    return nodeValue(node);
   }
 
   const child = node.firstChild;
@@ -378,6 +378,18 @@
   return label.search(/[^ *:()\u2013-]/) >= 0;
 }
 
+/**
+ * Returns the nodeValue in a way similar to the C++ version of node.nodeValue,
+ * used in src/components/autofill/content/renderer/form_autofill_util.h.
+ * Newlines and tabs are stripped.
+ *
+ * @param node A node to examine.
+ * @return The text contained in `element`.
+ */
+function nodeValue(node: Node): string {
+  return (node.nodeValue || '').replace(/[\n\t]/gm, '');
+}
+
 export {
   findChildTextWithIgnoreList,
   findChildText,
@@ -385,4 +397,5 @@
   ancestorTagNames,
   isTextAreaElement,
   isLabelValid,
+  nodeValue,
 };
diff --git a/components/autofill/ios/form_util/resources/fill_util.js b/components/autofill/ios/form_util/resources/fill_util.js
index 557edef..47db1c85 100644
--- a/components/autofill/ios/form_util/resources/fill_util.js
+++ b/components/autofill/ios/form_util/resources/fill_util.js
@@ -472,18 +472,6 @@
 };
 
 /**
- * Returns the nodeValue in a way similar to the C++ version of node.nodeValue,
- * used in src/components/autofill/content/renderer/form_autofill_util.h.
- * Newlines and tabs are stripped.
- *
- * @param {Node} node A node to examine.
- * @return {string} The text contained in |element|.
- */
-__gCrWeb.fill.nodeValue = function(node) {
-  return (node.nodeValue || '').replace(/[\n\t]/gm, '');
-};
-
-/**
  * Returns the value in a way similar to the C++ version of node.value,
  * used in src/components/autofill/content/renderer/form_autofill_util.h.
  * Newlines and tabs are stripped.
diff --git a/components/bookmarks/browser/bookmark_utils.cc b/components/bookmarks/browser/bookmark_utils.cc
index 72b6ed6..ed0cdb6 100644
--- a/components/bookmarks/browser/bookmark_utils.cc
+++ b/components/bookmarks/browser/bookmark_utils.cc
@@ -563,6 +563,8 @@
 std::u16string CleanUpUrlForMatching(
     const GURL& gurl,
     base::OffsetAdjuster::Adjustments* adjustments) {
+  DCHECK(gurl.is_valid());
+
   base::OffsetAdjuster::Adjustments tmp_adjustments;
   return base::i18n::ToLower(url_formatter::FormatUrlWithAdjustments(
       GURL(TruncateUrl(gurl.spec())),
diff --git a/components/bookmarks/browser/bookmark_utils.h b/components/bookmarks/browser/bookmark_utils.h
index 01cc41c89..58451993 100644
--- a/components/bookmarks/browser/bookmark_utils.h
+++ b/components/bookmarks/browser/bookmark_utils.h
@@ -171,6 +171,8 @@
 // unescaping, an input string of "a&p" would no longer match this URL.  Note
 // that the resulting unescaped URL may not be directly navigable (which is
 // why it was escaped to begin with).
+//
+// |url| must be a valid URL.
 std::u16string CleanUpUrlForMatching(
     const GURL& gurl,
     base::OffsetAdjuster::Adjustments* adjustments);
diff --git a/components/bookmarks/browser/bookmark_utils_unittest.cc b/components/bookmarks/browser/bookmark_utils_unittest.cc
index 1396615d..60fc32c 100644
--- a/components/bookmarks/browser/bookmark_utils_unittest.cc
+++ b/components/bookmarks/browser/bookmark_utils_unittest.cc
@@ -592,5 +592,12 @@
   EXPECT_EQ(1u, managed_node->children().size());
 }
 
+TEST_F(BookmarkUtilsTest, CleanUpUrlForMatching) {
+  EXPECT_EQ(u"http://foo.com/", CleanUpUrlForMatching(GURL("http://foo.com"),
+                                                      /*adjustments=*/nullptr));
+  EXPECT_EQ(u"http://foo.com/", CleanUpUrlForMatching(GURL("http://Foo.com"),
+                                                      /*adjustments=*/nullptr));
+}
+
 }  // namespace
 }  // namespace bookmarks
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..7b2b900 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
@@ -176,10 +176,7 @@
     }
 
     public void addEmbeddedPermission(ContentSettingException info) {
-        assert (info.getSecondaryPattern() == null && info.isEmbargoed())
-                || (info.getSecondaryPattern() != null
-                        && !info.getSecondaryPattern().equals("*")
-                        && !info.isEmbargoed());
+        assert !info.getSecondaryPattern().equals("*");
         var list = mEmbeddedPermissionInfos.computeIfAbsent(
                 info.getContentSettingType(), k -> new ArrayList<>());
         list.add(info);
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..1d78311f 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
@@ -163,7 +163,7 @@
      *
      * @param callback The callback to run when the fetch is complete.
      */
-    public void fetchAllPreferences(@NonNull WebsitePermissionsCallback callback) {
+    public void fetchAllPreferences(WebsitePermissionsCallback callback) {
         var fetcherInternal = new WebsitePermissionFetcherInternal();
         fetcherInternal.fetchAllPreferences(callback);
     }
@@ -175,7 +175,7 @@
      * @param callback The callback to run when the fetch is complete.
      */
     public void fetchPreferencesForCategory(
-            SiteSettingsCategory category, @NonNull WebsitePermissionsCallback callback) {
+            SiteSettingsCategory category, WebsitePermissionsCallback callback) {
         var fetcherInternal = new WebsitePermissionFetcherInternal();
         fetcherInternal.fetchPreferencesForCategory(category, callback);
     }
@@ -189,9 +189,8 @@
      * @param callback The callback to run when the fetch is complete.
      */
     public void fetchPreferencesForCategoryAndPopulateFpsInfo(
-            SiteSettingsDelegate siteSettingsDelegate,
-            SiteSettingsCategory category,
-            @NonNull WebsitePermissionsCallback callback) {
+            SiteSettingsDelegate siteSettingsDelegate, SiteSettingsCategory category,
+            WebsitePermissionsCallback callback) {
         var fetcherInternal = new WebsitePermissionFetcherInternal();
         fetcherInternal.fetchPreferencesForCategoryAndPopulateFpsInfo(
                 siteSettingsDelegate, category, callback);
@@ -212,7 +211,7 @@
          *
          * @param callback The callback to run when the fetch is complete.
          */
-        public void fetchAllPreferences(@NonNull WebsitePermissionsCallback callback) {
+        public void fetchAllPreferences(WebsitePermissionsCallback callback) {
             TaskQueue queue = new TaskQueue();
 
             addAllFetchers(queue);
@@ -239,7 +238,7 @@
          * @param callback The callback to run when the fetch is complete.
          */
         public void fetchPreferencesForCategory(
-                SiteSettingsCategory category, @NonNull WebsitePermissionsCallback callback) {
+                SiteSettingsCategory category, WebsitePermissionsCallback callback) {
             TaskQueue queue = createFetchersForCategory(category);
 
             queue.add(new PermissionsAvailableCallbackRunner(callback));
@@ -274,9 +273,8 @@
          * @param callback The callback to run when the fetch is complete.
          */
         public void fetchPreferencesForCategoryAndPopulateFpsInfo(
-                SiteSettingsDelegate siteSettingsDelegate,
-                SiteSettingsCategory category,
-                @NonNull WebsitePermissionsCallback callback) {
+                SiteSettingsDelegate siteSettingsDelegate, SiteSettingsCategory category,
+                WebsitePermissionsCallback callback) {
             TaskQueue queue = createFetchersForCategory(category);
             queue.add(new FirstPartySetsInfoFetcher(siteSettingsDelegate));
 
@@ -395,8 +393,6 @@
                 String embedder = exception.getSecondaryPattern();
 
                 if (isEmbeddedPermission
-                        && embedder != null
-                        && !embedder.equals(SITE_WILDCARD)
                         && mSiteSettingsCategory != null
                         && mSiteSettingsCategory.getType() == SiteSettingsCategory.Type.ALL_SITES) {
                     // AllSites should group embedded permissions by embedder.
@@ -661,10 +657,9 @@
         }
 
         private class PermissionsAvailableCallbackRunner extends Task {
-            private final @NonNull WebsitePermissionsCallback mCallback;
+            private final WebsitePermissionsCallback mCallback;
 
-            private PermissionsAvailableCallbackRunner(
-                    @NonNull WebsitePermissionsCallback callback) {
+            private PermissionsAvailableCallbackRunner(WebsitePermissionsCallback callback) {
                 mCallback = callback;
             }
 
diff --git a/components/browsing_topics/mojom/browsing_topics_internals.mojom b/components/browsing_topics/mojom/browsing_topics_internals.mojom
index fd34d1fd..b9113d5 100644
--- a/components/browsing_topics/mojom/browsing_topics_internals.mojom
+++ b/components/browsing_topics/mojom/browsing_topics_internals.mojom
@@ -27,9 +27,6 @@
   // enabled.
   bool browsing_topics_bypass_ip_is_publicly_routable_check_enabled;
 
-  // Whether the deprecatedBrowsingTopics XHR attribute is allowed.
-  bool browsing_topics_xhr_enabled;
-
   // Whether document.browsingTopics() is allowed.
   bool browsing_topics_document_api_enabled;
 
diff --git a/components/commerce/core/shopping_service_test_base.cc b/components/commerce/core/shopping_service_test_base.cc
index 2d6f101..7fff95f0 100644
--- a/components/commerce/core/shopping_service_test_base.cc
+++ b/components/commerce/core/shopping_service_test_base.cc
@@ -5,6 +5,7 @@
 #include "components/commerce/core/shopping_service_test_base.h"
 
 #include "base/command_line.h"
+#include "base/containers/contains.h"
 #include "base/containers/flat_map.h"
 #include "base/memory/ref_counted.h"
 #include "base/notreached.h"
@@ -290,8 +291,7 @@
 
   std::vector<DiscountClusterType> checked_cluster_types;
   for (const auto& info_to_check : infos) {
-    if (std::find(checked_cluster_types.begin(), checked_cluster_types.end(),
-                  info_to_check.cluster_type) != checked_cluster_types.end()) {
+    if (base::Contains(checked_cluster_types, info_to_check.cluster_type)) {
       continue;
     }
     checked_cluster_types.push_back(info_to_check.cluster_type);
diff --git a/components/content_settings/browser/content_settings_manager_impl.cc b/components/content_settings/browser/content_settings_manager_impl.cc
index 66b7449..0f5c867f 100644
--- a/components/content_settings/browser/content_settings_manager_impl.cc
+++ b/components/content_settings/browser/content_settings_manager_impl.cc
@@ -29,14 +29,13 @@
 namespace {
 using StorageType = mojom::ContentSettingsManager::StorageType;
 
-void OnStorageAccessed(int process_id,
-                       int frame_id,
+void OnStorageAccessed(const content::GlobalRenderFrameHostToken& frame_token,
                        const GURL& origin_url,
                        const GURL& top_origin_url,
                        bool blocked_by_policy,
                        page_load_metrics::StorageType storage_type) {
   content::RenderFrameHost* render_frame_host =
-      content::RenderFrameHost::FromID(process_id, frame_id);
+      content::RenderFrameHost::FromFrameToken(frame_token);
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(render_frame_host);
   if (!web_contents)
@@ -52,8 +51,7 @@
   }
 }
 
-void NotifyStorageAccess(int render_process_id,
-                         int32_t render_frame_id,
+void NotifyStorageAccess(const content::GlobalRenderFrameHostToken& frame_token,
                          StorageType storage_type,
                          const url::Origin& top_frame_origin,
                          bool allowed) {
@@ -73,8 +71,7 @@
     }
   })();
 
-  auto* rfh =
-      content::RenderFrameHost::FromID(render_process_id, render_frame_id);
+  auto* rfh = content::RenderFrameHost::FromFrameToken(frame_token);
 
   if (!rfh) {
     return;
@@ -101,23 +98,21 @@
 
   if (should_notify_pscs) {
     PageSpecificContentSettings::StorageAccessed(
-        storage_type, render_process_id, render_frame_id, rfh->GetStorageKey(),
-        !allowed);
+        storage_type, frame_token, rfh->GetStorageKey(), !allowed);
   }
 
   if (metrics_type) {
-    OnStorageAccessed(render_process_id, render_frame_id,
-                      rfh->GetLastCommittedURL(), top_frame_origin.GetURL(),
-                      !allowed, metrics_type.value());
+    OnStorageAccessed(frame_token, rfh->GetLastCommittedURL(),
+                      top_frame_origin.GetURL(), !allowed,
+                      metrics_type.value());
   }
 }
 
-void OnContentBlockedOnUI(int render_process_id,
-                          int32_t render_frame_id,
-                          ContentSettingsType type) {
+void OnContentBlockedOnUI(
+    const content::GlobalRenderFrameHostToken& frame_token,
+    ContentSettingsType type) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  PageSpecificContentSettings::ContentBlocked(render_process_id,
-                                              render_frame_id, type);
+  PageSpecificContentSettings::ContentBlocked(frame_token, type);
 }
 
 }  // namespace
@@ -152,7 +147,7 @@
 }
 
 void ContentSettingsManagerImpl::AllowStorageAccess(
-    int32_t render_frame_id,
+    const blink::LocalFrameToken& frame_token,
     StorageType storage_type,
     const url::Origin& origin,
     const net::SiteForCookies& site_for_cookies,
@@ -189,26 +184,31 @@
   if (!allowed && net::cookie_util::IsForceThirdPartyCookieBlockingEnabled()) {
     allowed = true;
   }
-  if (delegate_->AllowStorageAccess(render_process_id_, render_frame_id,
-                                    storage_type, url, allowed, &callback)) {
+  if (delegate_->AllowStorageAccess(
+          content::GlobalRenderFrameHostToken(render_process_id_, frame_token),
+          storage_type, url, allowed, &callback)) {
     DCHECK(!callback);
     return;
   }
 
   content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE,
-      base::BindOnce(&NotifyStorageAccess, render_process_id_, render_frame_id,
-                     storage_type, top_frame_origin, allowed));
+      FROM_HERE, base::BindOnce(&NotifyStorageAccess,
+                                content::GlobalRenderFrameHostToken(
+                                    render_process_id_, frame_token),
+                                storage_type, top_frame_origin, allowed));
 
   std::move(callback).Run(allowed);
 }
 
-void ContentSettingsManagerImpl::OnContentBlocked(int32_t render_frame_id,
-                                                  ContentSettingsType type) {
+void ContentSettingsManagerImpl::OnContentBlocked(
+    const blink::LocalFrameToken& frame_token,
+    ContentSettingsType type) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   content::GetUIThreadTaskRunner({})->PostTask(
-      FROM_HERE, base::BindOnce(&OnContentBlockedOnUI, render_process_id_,
-                                render_frame_id, type));
+      FROM_HERE, base::BindOnce(&OnContentBlockedOnUI,
+                                content::GlobalRenderFrameHostToken(
+                                    render_process_id_, frame_token),
+                                type));
 }
 
 ContentSettingsManagerImpl::ContentSettingsManagerImpl(
diff --git a/components/content_settings/browser/content_settings_manager_impl.h b/components/content_settings/browser/content_settings_manager_impl.h
index 0f859acc..533d01e 100644
--- a/components/content_settings/browser/content_settings_manager_impl.h
+++ b/components/content_settings/browser/content_settings_manager_impl.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/ref_counted.h"
 #include "components/content_settings/common/content_settings_manager.mojom.h"
+#include "content/public/browser/global_routing_id.h"
 
 namespace content {
 class BrowserContext;
@@ -38,8 +39,7 @@
     // true here, the default logic will be bypassed. Can be called on any
     // thread.
     virtual bool AllowStorageAccess(
-        int render_process_id,
-        int render_frame_id,
+        const content::GlobalRenderFrameHostToken& frame_token,
         StorageType storage_type,
         const GURL& url,
         bool allowed,
@@ -61,13 +61,13 @@
   void Clone(
       mojo::PendingReceiver<content_settings::mojom::ContentSettingsManager>
           receiver) override;
-  void AllowStorageAccess(int32_t render_frame_id,
+  void AllowStorageAccess(const blink::LocalFrameToken& frame_token,
                           StorageType storage_type,
                           const url::Origin& origin,
                           const net::SiteForCookies& site_for_cookies,
                           const url::Origin& top_frame_origin,
                           base::OnceCallback<void(bool)> callback) override;
-  void OnContentBlocked(int32_t render_frame_id,
+  void OnContentBlocked(const blink::LocalFrameToken& frame_token,
                         ContentSettingsType type) override;
 
  private:
diff --git a/components/content_settings/browser/page_specific_content_settings.cc b/components/content_settings/browser/page_specific_content_settings.cc
index 7120c82f..15c1540 100644
--- a/components/content_settings/browser/page_specific_content_settings.cc
+++ b/components/content_settings/browser/page_specific_content_settings.cc
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/command_line.h"
+#include "base/functional/overloaded.h"
 #include "base/lazy_instance.h"
 #include "base/memory/ptr_util.h"
 #include "base/observer_list.h"
@@ -667,16 +668,25 @@
 // static
 void PageSpecificContentSettings::StorageAccessed(
     StorageType storage_type,
-    int render_process_id,
-    int render_frame_id,
+    absl::variant<content::GlobalRenderFrameHostToken,
+                  content::GlobalRenderFrameHostId> frame_id,
     const blink::StorageKey& storage_key,
     bool blocked_by_policy) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  content::RenderFrameHost* rfh =
-      content::RenderFrameHost::FromID(render_process_id, render_frame_id);
+  content::RenderFrameHost* rfh = absl::visit(
+      base::Overloaded{
+          [](const content::GlobalRenderFrameHostToken& frame_token) {
+            return content::RenderFrameHost::FromFrameToken(frame_token);
+          },
+          [](const content::GlobalRenderFrameHostId& id) {
+            return content::RenderFrameHost::FromID(id);
+          },
+      },
+      frame_id);
+
   if (DelayUntilCommitIfNecessary(
           rfh, &PageSpecificContentSettings::StorageAccessed, storage_type,
-          render_process_id, render_frame_id, storage_key, blocked_by_policy)) {
+          frame_id, storage_key, blocked_by_policy)) {
     return;
   }
   PageSpecificContentSettings* settings = GetForFrame(rfh);
@@ -735,14 +745,14 @@
 }
 
 // static
-void PageSpecificContentSettings::ContentBlocked(int render_process_id,
-                                                 int render_frame_id,
-                                                 ContentSettingsType type) {
+void PageSpecificContentSettings::ContentBlocked(
+    const content::GlobalRenderFrameHostToken& frame_token,
+    ContentSettingsType type) {
   content::RenderFrameHost* rfh =
-      content::RenderFrameHost::FromID(render_process_id, render_frame_id);
+      content::RenderFrameHost::FromFrameToken(frame_token);
   if (DelayUntilCommitIfNecessary(rfh,
                                   &PageSpecificContentSettings::ContentBlocked,
-                                  render_process_id, render_frame_id, type)) {
+                                  frame_token, type)) {
     return;
   }
   PageSpecificContentSettings* settings = GetForFrame(rfh);
diff --git a/components/content_settings/browser/page_specific_content_settings.h b/components/content_settings/browser/page_specific_content_settings.h
index 451657b..bc78033 100644
--- a/components/content_settings/browser/page_specific_content_settings.h
+++ b/components/content_settings/browser/page_specific_content_settings.h
@@ -34,6 +34,7 @@
 #include "content/public/browser/page_user_data.h"
 #include "content/public/browser/render_frame_host.h"
 #include "net/base/schemeful_site.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
 #include "url/gurl.h"
 
 namespace blink {
@@ -250,8 +251,8 @@
 
   static void StorageAccessed(
       mojom::ContentSettingsManager::StorageType storage_type,
-      int render_process_id,
-      int render_frame_id,
+      absl::variant<content::GlobalRenderFrameHostToken,
+                    content::GlobalRenderFrameHostId> frame_id,
       const blink::StorageKey& storage_key,
       bool blocked_by_policy);
 
@@ -261,9 +262,9 @@
                                    bool blocked);
 
   // Called when content access is blocked in the renderer process.
-  static void ContentBlocked(int render_process_id,
-                             int render_frame_id,
-                             ContentSettingsType type);
+  static void ContentBlocked(
+      const content::GlobalRenderFrameHostToken& frame_token,
+      ContentSettingsType type);
 
   // Called when a specific Shared Worker was accessed.
   static void SharedWorkerAccessed(int render_process_id,
diff --git a/components/content_settings/common/BUILD.gn b/components/content_settings/common/BUILD.gn
index cd1c8a5..04c77e7 100644
--- a/components/content_settings/common/BUILD.gn
+++ b/components/content_settings/common/BUILD.gn
@@ -15,6 +15,7 @@
   deps = [
     "//components/content_settings/core/common:mojo_bindings",
     "//services/network/public/mojom:cookies_mojom",
+    "//third_party/blink/public/mojom/tokens",
     "//url/mojom:url_mojom_gurl",
     "//url/mojom:url_mojom_origin",
   ]
diff --git a/components/content_settings/common/content_settings_manager.mojom b/components/content_settings/common/content_settings_manager.mojom
index 29040f6..dc737d0 100644
--- a/components/content_settings/common/content_settings_manager.mojom
+++ b/components/content_settings/common/content_settings_manager.mojom
@@ -6,6 +6,7 @@
 
 import "components/content_settings/core/common/content_settings.mojom";
 import "services/network/public/mojom/site_for_cookies.mojom";
+import "third_party/blink/public/mojom/tokens/tokens.mojom";
 import "url/mojom/origin.mojom";
 
 // An interface to the content settings manager running in the browser process
@@ -33,7 +34,7 @@
   // Then these parameters would not need to be passed here.
   [Sync]
   AllowStorageAccess(
-      int32 render_frame_id,
+      blink.mojom.LocalFrameToken frame_token,
       StorageType storage_type,
       url.mojom.Origin origin,
       network.mojom.SiteForCookies site_for_cookies,
@@ -41,5 +42,6 @@
 
   // Tells the browser that content in the current page was blocked due to the
   // user's content settings.
-  OnContentBlocked(int32 render_frame_id, ContentSettingsType type);
+  OnContentBlocked(blink.mojom.LocalFrameToken frame_token,
+                   ContentSettingsType type);
 };
diff --git a/components/content_settings/renderer/content_settings_agent_impl.cc b/components/content_settings/renderer/content_settings_agent_impl.cc
index c2813e9..ea7d442 100644
--- a/components/content_settings/renderer/content_settings_agent_impl.cc
+++ b/components/content_settings/renderer/content_settings_agent_impl.cc
@@ -118,7 +118,8 @@
     ContentSettingsType settings_type) {
   bool newly_blocked = content_blocked_.insert(settings_type).second;
   if (newly_blocked)
-    GetContentSettingsManager().OnContentBlocked(routing_id(), settings_type);
+    GetContentSettingsManager().OnContentBlocked(
+        render_frame()->GetWebFrame()->GetLocalFrameToken(), settings_type);
 }
 
 namespace {
@@ -255,7 +256,7 @@
       std::move(callback), key, std::ref(cached_storage_permissions_));
 
   GetContentSettingsManager().AllowStorageAccess(
-      routing_id(), ConvertToMojoStorageType(storage_type),
+      frame->GetLocalFrameToken(), ConvertToMojoStorageType(storage_type),
       frame->GetSecurityOrigin(), frame->GetDocument().SiteForCookies(),
       frame->GetDocument().TopFrameOrigin(), std::move(new_cb));
 }
@@ -275,7 +276,7 @@
   SCOPED_UMA_HISTOGRAM_TIMER("ContentSettings.AllowStorageAccessSync");
   bool result = false;
   GetContentSettingsManager().AllowStorageAccess(
-      routing_id(), ConvertToMojoStorageType(storage_type),
+      frame->GetLocalFrameToken(), ConvertToMojoStorageType(storage_type),
       frame->GetSecurityOrigin(), frame->GetDocument().SiteForCookies(),
       frame->GetDocument().TopFrameOrigin(), &result);
   cached_storage_permissions_[key] = result;
diff --git a/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc b/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc
index a411e4d..70cb8fc 100644
--- a/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc
+++ b/components/content_settings/renderer/content_settings_agent_impl_browsertest.cc
@@ -70,7 +70,7 @@
       mojo::PendingReceiver<mojom::ContentSettingsManager> receiver) override {
     ADD_FAILURE() << "Not reached";
   }
-  void AllowStorageAccess(int32_t render_frame_id,
+  void AllowStorageAccess(const blink::LocalFrameToken& frame_token,
                           StorageType storage_type,
                           const url::Origin& origin,
                           const net::SiteForCookies& site_for_cookies,
@@ -79,7 +79,7 @@
     ++log_->allow_storage_access_count;
     std::move(callback).Run(true);
   }
-  void OnContentBlocked(int32_t render_frame_id,
+  void OnContentBlocked(const blink::LocalFrameToken& frame_token,
                         ContentSettingsType type) override {
     ++log_->on_content_blocked_count;
     log_->on_content_blocked_type = type;
diff --git a/components/embedder_support/content_settings_utils.cc b/components/embedder_support/content_settings_utils.cc
index 751d7a7..9013d397 100644
--- a/components/embedder_support/content_settings_utils.cc
+++ b/components/embedder_support/content_settings_utils.cc
@@ -71,8 +71,7 @@
       continue;
     }
     content_settings::PageSpecificContentSettings::StorageAccessed(
-        storage_type, it.child_id, it.frame_routing_id, rfh->GetStorageKey(),
-        !allow);
+        storage_type, it, rfh->GetStorageKey(), !allow);
   }
 
   return allow;
diff --git a/components/exo/wayland/zcr_remote_shell_impl.cc b/components/exo/wayland/zcr_remote_shell_impl.cc
index 9b601cf..ec54f11 100644
--- a/components/exo/wayland/zcr_remote_shell_impl.cc
+++ b/components/exo/wayland/zcr_remote_shell_impl.cc
@@ -573,7 +573,8 @@
   in_display_update_ = true;
 }
 
-void WaylandRemoteShell::OnDidProcessDisplayChanges() {
+void WaylandRemoteShell::OnDidProcessDisplayChanges(
+    const DisplayConfigurationChange& configuration_change) {
   in_display_update_ = false;
 }
 
diff --git a/components/exo/wayland/zcr_remote_shell_impl.h b/components/exo/wayland/zcr_remote_shell_impl.h
index 3a1f0e1..ff426a1b 100644
--- a/components/exo/wayland/zcr_remote_shell_impl.h
+++ b/components/exo/wayland/zcr_remote_shell_impl.h
@@ -101,7 +101,8 @@
 
   // display::DisplayManagerObserver:
   void OnWillProcessDisplayChanges() override;
-  void OnDidProcessDisplayChanges() override;
+  void OnDidProcessDisplayChanges(
+      const DisplayConfigurationChange& configuration_change) override;
 
   // Overridden from ash::TabletModeObserver:
   void OnTabletModeStarted() override;
diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc
index 427a791f..f541990 100644
--- a/components/history/core/browser/history_backend.cc
+++ b/components/history/core/browser/history_backend.cc
@@ -944,6 +944,7 @@
 
 void HistoryBackend::AddPage(const HistoryAddPageArgs& request) {
   TRACE_EVENT0("browser", "HistoryBackend::AddPage");
+  DCHECK(request.url.is_valid());
 
   if (!db_)
     return;
@@ -1116,6 +1117,8 @@
 
     for (size_t redirect_index = 0; redirect_index < redirects.size();
          redirect_index++) {
+      DCHECK(redirects[redirect_index].is_valid());
+
       constexpr int kRedirectQualifiers = ui::PAGE_TRANSITION_CHAIN_START |
                                           ui::PAGE_TRANSITION_CHAIN_END |
                                           ui::PAGE_TRANSITION_IS_REDIRECT_MASK;
@@ -1360,6 +1363,7 @@
     absl::optional<VisitID> originator_referring_visit,
     absl::optional<VisitID> originator_opener_visit,
     bool is_known_to_sync) {
+  DCHECK(url.is_valid());
   // See if this URL is already in the DB.
   URLRow url_info(url);
   URLID url_id = db_->GetRowForURL(url, &url_info);
@@ -1594,6 +1598,7 @@
 void HistoryBackend::AddPageNoVisitForBookmark(const GURL& url,
                                                const std::u16string& title) {
   TRACE_EVENT0("browser", "HistoryBackend::AddPageNoVisitForBookmark");
+  DCHECK(url.is_valid());
 
   if (!db_)
     return;
@@ -1690,6 +1695,8 @@
     return kInvalidVisitID;
   }
 
+  DCHECK(url.is_valid());
+
   auto [url_id, visit_id] = AddPageVisit(
       url, visit.visit_time, visit.referring_visit, visit.external_referrer_url,
       visit.transition, hidden, VisitSource::SOURCE_SYNCED,
@@ -3100,6 +3107,7 @@
         // url is bookmarked. The same is applicable to the saved credential's
         // URLs.
         if (backend_client_ && backend_client_->IsPinnedURL(url)) {
+          DCHECK(url.is_valid());
           URLRow url_info(url);
           url_info.set_visit_count(0);
           url_info.set_typed_count(0);
diff --git a/components/history/core/browser/history_backend_unittest.cc b/components/history/core/browser/history_backend_unittest.cc
index 30e7486..f16c8932 100644
--- a/components/history/core/browser/history_backend_unittest.cc
+++ b/components/history/core/browser/history_backend_unittest.cc
@@ -4490,8 +4490,10 @@
 
 TEST_F(HistoryBackendTest, GetRedirectChainStart) {
   auto last_visit_time = base::Time::Now();
-  const auto add_visit = [&](std::string url, VisitID referring_visit,
+  const auto add_visit = [&](std::string url_string, VisitID referring_visit,
                              VisitID opener_visit, bool is_redirect) {
+    GURL url(url_string);
+    EXPECT_TRUE(url.is_valid()) << url_string;
     // Each visit should have a unique `visit_time` to avoid deduping visits so
     // the same URL. The exact times don't matter, but we use increasing values
     // to make the test cases easy to reason about.
@@ -4503,16 +4505,16 @@
         ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_CHAIN_END |
         (is_redirect ? ui::PageTransition::PAGE_TRANSITION_IS_REDIRECT_MASK
                      : ui::PageTransition::PAGE_TRANSITION_CHAIN_START));
-    auto ids = backend_->AddPageVisit(
-        GURL(url), last_visit_time, referring_visit,
-        /*external_referrer_url=*/GURL(), transition, false, SOURCE_BROWSED,
-        false, opener_visit, true);
+    auto ids = backend_->AddPageVisit(url, last_visit_time, referring_visit,
+                                      /*external_referrer_url=*/GURL(),
+                                      transition, false, SOURCE_BROWSED, false,
+                                      opener_visit, true);
     backend_->AddContextAnnotationsForVisit(ids.second,
                                             VisitContextAnnotations());
   };
 
-  // Navigate to 'google.com'.
-  add_visit("google.com", 0, 0, false);
+  // Navigate to 'http://google.com'.
+  add_visit("http://google.com", 0, 0, false);
   // It redirects to 'https://www.google.com'.
   add_visit("https://www.google.com", 1, 0, true);
   // Perform a search.
diff --git a/components/history/core/browser/history_service.cc b/components/history/core/browser/history_service.cc
index 898406b5..f731dd1 100644
--- a/components/history/core/browser/history_service.cc
+++ b/components/history/core/browser/history_service.cc
@@ -21,6 +21,7 @@
 
 #include "base/command_line.h"
 #include "base/compiler_specific.h"
+#include "base/containers/cxx20_erase_vector.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/location.h"
@@ -91,7 +92,7 @@
         can_add_url_(can_add_url) {}
 
   bool CanAddURL(const GURL& url) const override {
-    return can_add_url_ ? can_add_url_.Run(url) : true;
+    return can_add_url_ ? can_add_url_.Run(url) : url.is_valid();
   }
 
   void NotifyProfileError(sql::InitStatus init_status,
@@ -589,7 +590,7 @@
       /*did_replace_entry=*/false, /*consider_for_ntp_most_visited=*/true));
 }
 
-void HistoryService::AddPage(const HistoryAddPageArgs& add_page_args) {
+void HistoryService::AddPage(HistoryAddPageArgs add_page_args) {
   TRACE_EVENT0("browser", "HistoryService::AddPage");
   DCHECK(backend_task_runner_) << "History service being called after cleanup";
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -597,6 +598,11 @@
   if (!CanAddURL(add_page_args.url))
     return;
 
+  DCHECK(add_page_args.url.is_valid());
+
+  base::EraseIf(add_page_args.redirects,
+                [this](const GURL& url) { return !CanAddURL(url); });
+
   // Inform VisitedDelegate of all links and redirects.
   if (visit_delegate_) {
     if (!add_page_args.redirects.empty()) {
@@ -632,6 +638,8 @@
   if (!CanAddURL(url))
     return;
 
+  DCHECK(url.is_valid());
+
   ScheduleTask(PRIORITY_NORMAL,
                base::BindOnce(&HistoryBackend::AddPageNoVisitForBookmark,
                               history_backend_, url, title));
@@ -768,6 +776,8 @@
   if (!CanAddURL(url))
     return;
 
+  DCHECK(url.is_valid());
+
   // Inform VisitDelegate of the URL.
   if (visit_delegate_) {
     visit_delegate_->AddURL(url);
@@ -1734,7 +1744,7 @@
 
 bool HistoryService::CanAddURL(const GURL& url) {
   if (!history_client_) {
-    return true;
+    return url.is_valid();
   }
   return history_client_->GetThreadSafeCanAddURLCallback().Run(url);
 }
diff --git a/components/history/core/browser/history_service.h b/components/history/core/browser/history_service.h
index d445938f..52712af 100644
--- a/components/history/core/browser/history_service.h
+++ b/components/history/core/browser/history_service.h
@@ -204,7 +204,7 @@
   void AddPage(const GURL& url, base::Time time, VisitSource visit_source);
 
   // All AddPage variants end up here.
-  void AddPage(const HistoryAddPageArgs& add_page_args);
+  void AddPage(HistoryAddPageArgs add_page_args);
 
   // Adds an entry for the specified url without creating a visit. This should
   // only be used when bookmarking a page, otherwise the row leaks in the
diff --git a/components/history/core/browser/url_database.cc b/components/history/core/browser/url_database.cc
index 4141467..0e64ef5 100644
--- a/components/history/core/browser/url_database.cc
+++ b/components/history/core/browser/url_database.cc
@@ -35,9 +35,10 @@
 URLDatabase::URLEnumerator::URLEnumerator() = default;
 
 bool URLDatabase::URLEnumerator::GetNextURL(URLRow* r) {
-  if (statement_.Step()) {
-    FillURLRow(statement_, r);
-    return true;
+  while (statement_.Step()) {
+    if (FillURLRow(statement_, r)) {
+      return true;
+    }
   }
   return false;
 }
@@ -48,17 +49,22 @@
 
 URLDatabase::~URLDatabase() = default;
 
-// Convenience to fill a URLRow. Must be in sync with the fields in
-// kURLRowFields.
-void URLDatabase::FillURLRow(sql::Statement& s, URLRow* i) {
+bool URLDatabase::FillURLRow(sql::Statement& s, URLRow* i) {
   DCHECK(i);
+
+  GURL url(s.ColumnString(1));
+  if (!url.is_valid()) {
+    return false;
+  }
+
   i->set_id(s.ColumnInt64(0));
-  i->set_url(GURL(s.ColumnString(1)));
+  i->set_url(url);
   i->set_title(s.ColumnString16(2));
   i->set_visit_count(s.ColumnInt(3));
   i->set_typed_count(s.ColumnInt(4));
   i->set_last_visit(s.ColumnTime(5));
   i->set_hidden(s.ColumnInt(6) != 0);
+  return true;
 }
 
 bool URLDatabase::MigrateKeywordsSearchTermsLowerTermColumn() {
@@ -120,8 +126,7 @@
   statement.BindInt64(0, url_id);
 
   if (statement.Step()) {
-    FillURLRow(statement, info);
-    return true;
+    return FillURLRow(statement, info);
   }
   return false;
 }
@@ -132,11 +137,14 @@
   std::string url_string = database_utils::GurlToDatabaseUrl(url);
   statement.BindString(0, url_string);
 
-  if (!statement.Step())
-    return 0;  // no data
+  if (!statement.Step()) {
+    return 0;  // No data.
+  }
 
-  if (info)
-    FillURLRow(statement, info);
+  if (info && !FillURLRow(statement, info)) {
+    return 0;  // Invalid URL row.
+  }
+
   return statement.ColumnInt64(0);
 }
 
@@ -345,9 +353,9 @@
 
   while (statement.Step()) {
     URLRow info;
-    FillURLRow(statement, &info);
-    if (info.url().is_valid())
+    if (FillURLRow(statement, &info)) {
       results->push_back(info);
+    }
   }
   return !results->empty();
 }
@@ -378,6 +386,8 @@
                                           int min_typed,
                                           bool allow_base,
                                           URLRow* info) {
+  DCHECK(info);
+
   // Select URLs that start with `base` and are prefixes of `url`.  All parts
   // of this query except the substr() call can be done using the index.  We
   // could do this query with a couple of LIKE or GLOB statements as well, but
@@ -387,21 +397,26 @@
   sql.append(kURLRowFields);
   sql.append(" FROM urls WHERE url ");
   sql.append(allow_base ? ">=" : ">");
-  sql.append(" ? AND url < :end AND url = substr(:end, 1, length(url)) "
-             "AND hidden = 0 AND visit_count >= ? AND typed_count >= ? "
-             "ORDER BY url LIMIT 1");
+  // Avoid limiting to 1 read to guard against the hypothetical case that a
+  // URL stored in the database is invalid, which requires moving on to the
+  // next best.
+  sql.append(
+      " ? AND url < :end AND url = substr(:end, 1, length(url)) "
+      "AND hidden = 0 AND visit_count >= ? AND typed_count >= ? "
+      "ORDER BY url");
   sql::Statement statement(GetDB().GetUniqueStatement(sql.c_str()));
   statement.BindString(0, base);
   statement.BindString(1, url);   // :end
   statement.BindInt(2, min_visits);
   statement.BindInt(3, min_typed);
 
-  if (!statement.Step())
-    return false;
+  while (statement.Step()) {
+    if (FillURLRow(statement, info)) {
+      return true;
+    }
+  }
 
-  DCHECK(info);
-  FillURLRow(statement, info);
-  return true;
+  return false;
 }
 
 URLRows URLDatabase::GetTextMatches(const std::u16string& query) {
@@ -436,9 +451,9 @@
 
     if (query_parser::QueryParser::DoesQueryMatch(query_words, query_nodes)) {
       URLResult info;
-      FillURLRow(statement, &info);
-      if (info.url().is_valid())
+      if (FillURLRow(statement, &info)) {
         results.push_back(info);
+      }
     }
   }
   return results;
diff --git a/components/history/core/browser/url_database.h b/components/history/core/browser/url_database.h
index 336374d..6385f7c6 100644
--- a/components/history/core/browser/url_database.h
+++ b/components/history/core/browser/url_database.h
@@ -297,8 +297,9 @@
   bool URLTableContainsAutoincrement();
 
   // Convenience to fill a URLRow. Must be in sync with the fields in
-  // kHistoryURLRowFields.
-  static void FillURLRow(sql::Statement& s, URLRow* i);
+  // kHistoryURLRowFields. Returns true if the data was valid and |*i| was
+  // actually populated.
+  [[nodiscard]] static bool FillURLRow(sql::Statement& s, URLRow* i);
 
   // Returns the database for the functions in this interface. The descendant of
   // this class implements these functions to return its objects.
diff --git a/components/history_clusters/core/history_clusters_service_unittest.cc b/components/history_clusters/core/history_clusters_service_unittest.cc
index 5e1d80a..0f1f1357 100644
--- a/components/history_clusters/core/history_clusters_service_unittest.cc
+++ b/components/history_clusters/core/history_clusters_service_unittest.cc
@@ -210,6 +210,8 @@
     add_page_args.context_id = context_id;
     add_page_args.nav_entry_id = next_navigation_id_;
     add_page_args.url = visit.url_row.url();
+    EXPECT_TRUE(add_page_args.url.is_valid())
+        << " for URL \"" << add_page_args.url.possibly_invalid_spec() << "\"";
     add_page_args.title = visit.url_row.title();
     add_page_args.time = visit.visit_row.visit_time;
     add_page_args.visit_source = visit.source;
@@ -238,6 +240,7 @@
   void AddCompleteVisit(history::VisitID visit_id, base::Time visit_time) {
     history::AnnotatedVisit visit;
     visit.url_row.set_id(1);
+    visit.url_row.set_url(GURL("https://foo.com"));
     visit.visit_row.visit_id = visit_id;
     visit.visit_row.visit_time = visit_time;
     visit.source = history::VisitSource::SOURCE_BROWSED;
diff --git a/components/lens/lens_metrics.h b/components/lens/lens_metrics.h
index b061982..18b42d76 100644
--- a/components/lens/lens_metrics.h
+++ b/components/lens/lens_metrics.h
@@ -40,7 +40,13 @@
   TASKS_SURFACE = 3,
   KEYBOARD = 4,
   SPOTLIGHT = 5,
-  kMaxValue = SPOTLIGHT
+  APP_ICON_LONG_PRESS = 6,
+  PLUS_BUTTON = 7,
+  WEB_SEARCH_BAR = 8,
+  TRANSLATE_ONEBOX = 9,
+  INTENTS = 10,
+  WEB_IMAGES_SEARCH_BAR = 11,
+  kMaxValue = WEB_IMAGES_SEARCH_BAR
 };
 
 // Needs to be kept in sync with CameraResult enum in
@@ -71,7 +77,9 @@
   WEB_SEARCH_BAR = 12,
   COMPANION_REGION_SEARCH = 13,
   TRANSLATE_ONEBOX = 14,
-  kMaxValue = TRANSLATE_ONEBOX
+  INTENTS = 15,
+  WEB_IMAGES_SEARCH_BAR = 16,
+  kMaxValue = WEB_IMAGES_SEARCH_BAR
 };
 
 // This should be kept in sync with the LensRegionSearchAspectRatio enum
diff --git a/components/metrics/generate_expired_histograms_array.gni b/components/metrics/generate_expired_histograms_array.gni
index 0bba60be..adb4a2a 100644
--- a/components/metrics/generate_expired_histograms_array.gni
+++ b/components/metrics/generate_expired_histograms_array.gni
@@ -179,6 +179,7 @@
       "//tools/metrics/histograms/metadata/safe_browsing/histograms.xml",
       "//tools/metrics/histograms/metadata/sb_client/enums.xml",
       "//tools/metrics/histograms/metadata/sb_client/histograms.xml",
+      "//tools/metrics/histograms/metadata/scanning/enums.xml",
       "//tools/metrics/histograms/metadata/scanning/histograms.xml",
       "//tools/metrics/histograms/metadata/scheduler/histograms.xml",
       "//tools/metrics/histograms/metadata/search/histograms.xml",
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 195b64f..f21a0897 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -971,7 +971,7 @@
       }
 
       if (omnibox_feature_configs::ForceAllowedToBeDefault::Get().enabled)
-        match->allowed_to_be_default_match = true;
+        match->SetAllowedToBeDefault(input_);
     }
 
     internal_result_.MergeSuggestionGroupsMap(
diff --git a/components/omnibox/browser/history_url_provider.cc b/components/omnibox/browser/history_url_provider.cc
index a47205aa..f85b4e9 100644
--- a/components/omnibox/browser/history_url_provider.cc
+++ b/components/omnibox/browser/history_url_provider.cc
@@ -245,7 +245,7 @@
 
 // Given the user's `input` and a `match` created from it, reduce the match's
 // URL to just a host.  If this host still matches the user input, return it.
-// Returns the empty string on failure.
+// Returns the empty URL on failure.
 GURL ConvertToHostOnly(const history::HistoryMatch& match,
                        const std::u16string& input) {
   // See if we should try to do host-only suggestions for this URL. Nonstandard
@@ -971,7 +971,7 @@
   const history::HistoryMatch& match = params->matches[0];
   GURL search_base = ConvertToHostOnly(match, params->input.text());
   bool can_add_search_base_to_matches = !params->have_what_you_typed_match;
-  if (search_base.is_empty()) {
+  if (!search_base.is_valid()) {
     // Search from what the user typed when we couldn't reduce the best match
     // to a host.  Careful: use a substring of `match` here, rather than the
     // first match in `params`, because they might have different prefixes.  If
@@ -982,8 +982,9 @@
     std::string new_match = match.url_info.url().possibly_invalid_spec().substr(
         0, match.input_location + params->input.text().length());
     search_base = GURL(new_match);
-    if (search_base.is_empty())
+    if (!search_base.is_valid()) {
       return false;  // Can't construct a URL from which to start a search.
+    }
   } else if (!can_add_search_base_to_matches) {
     can_add_search_base_to_matches =
         (search_base != params->what_you_typed_match.destination_url);
diff --git a/components/omnibox/browser/omnibox_feature_configs.cc b/components/omnibox/browser/omnibox_feature_configs.cc
index 125414ca..ea7e133 100644
--- a/components/omnibox/browser/omnibox_feature_configs.cc
+++ b/components/omnibox/browser/omnibox_feature_configs.cc
@@ -64,6 +64,10 @@
       base::FeatureParam<int>(&kShortcutBoost,
                               "ShortcutBoostNonTopHitThreshold", 2)
           .Get();
+  non_top_hit_search_threshold =
+      base::FeatureParam<int>(&kShortcutBoost,
+                              "ShortcutBoostNonTopHitSearchThreshold", 2)
+          .Get();
   group_with_searches =
       base::FeatureParam<bool>(&kShortcutBoost,
                                "ShortcutBoostGroupWithSearches", true)
diff --git a/components/omnibox/browser/omnibox_feature_configs.h b/components/omnibox/browser/omnibox_feature_configs.h
index 0a49d77..2f72a1f 100644
--- a/components/omnibox/browser/omnibox_feature_configs.h
+++ b/components/omnibox/browser/omnibox_feature_configs.h
@@ -132,11 +132,12 @@
   bool counterfactual;
   // Shortcuts are boosted if either:
   // 1) They are the top shortcut.
-  // 2) OR they have more hits than `non_top_hit_threshold`. If this is 1, then
-  //    all shortcuts are boosted, since all have at least 1 hit. If 0
-  //    (default), then no shortcuts will be boosted through (2) - only the top
-  //    shortcut will be boosted.
+  // 2) OR they have more hits than `non_top_hit_[searches_]threshold`. If this
+  //    is 1, then all shortcuts are boosted, since all have at least 1 hit. If
+  //    0 (default), then no shortcuts will be boosted through (2) - only the
+  //    top shortcut will be boosted.
   int non_top_hit_threshold;
+  int non_top_hit_search_threshold;
   // If enabled, boosted shortcuts will be grouped with searches. Unboosted
   // shortcuts are grouped with URLs, like traditionally, regardless of
   // `group_with_searches`.
diff --git a/components/omnibox/browser/scored_history_match_unittest.cc b/components/omnibox/browser/scored_history_match_unittest.cc
index e5edcc1..976f713 100644
--- a/components/omnibox/browser/scored_history_match_unittest.cc
+++ b/components/omnibox/browser/scored_history_match_unittest.cc
@@ -127,6 +127,7 @@
                                                    int days_since_last_visit,
                                                    int typed_count) {
   history::URLRow row(GURL(url), 0);
+  EXPECT_TRUE(row.url().is_valid());
   row.set_title(ASCIIToUTF16(title));
   row.set_visit_count(visit_count);
   row.set_typed_count(typed_count);
@@ -901,7 +902,7 @@
 TEST_F(ScoredHistoryMatchTest, GetDomainRelevancyScore) {
   auto domain_relevancy_score = [&](base::TimeDelta time_since_last_visit) {
     auto now = base::Time::Now();
-    history::URLRow row;
+    history::URLRow row(GURL("http://foo.com"));
     ScoredHistoryMatchPublic match(row, {}, u"", Make1Term("x"), {}, {}, false,
                                    1, false, {});
     match.url_info.set_last_visit(now - time_since_last_visit);
diff --git a/components/omnibox/browser/shortcuts_provider.cc b/components/omnibox/browser/shortcuts_provider.cc
index 94d83b9..2510495 100644
--- a/components/omnibox/browser/shortcuts_provider.cc
+++ b/components/omnibox/browser/shortcuts_provider.cc
@@ -420,10 +420,15 @@
   DCHECK_GT(shortcuts.size(), 0u);
 
   const int number_of_hits = SumNumberOfHits(shortcuts);
+  const int number_of_hits_threshold =
+      AutocompleteMatch::IsSearchType(shortcuts[0]->match_core.type)
+          ? omnibox_feature_configs::ShortcutBoosting::Get()
+                .non_top_hit_search_threshold
+          : omnibox_feature_configs::ShortcutBoosting::Get()
+                .non_top_hit_threshold;
+
   int boost_score = 0;
-  if (omnibox_feature_configs::ShortcutBoosting::Get().non_top_hit_threshold &&
-      number_of_hits >= omnibox_feature_configs::ShortcutBoosting::Get()
-                            .non_top_hit_threshold) {
+  if (number_of_hits_threshold && number_of_hits >= number_of_hits_threshold) {
     boost_score =
         AutocompleteMatch::IsSearchType(shortcuts[0]->match_core.type)
             ? omnibox_feature_configs::ShortcutBoosting::Get().search_score
diff --git a/components/omnibox/browser/shortcuts_provider_unittest.cc b/components/omnibox/browser/shortcuts_provider_unittest.cc
index 5360d5a..d3c47fbf 100644
--- a/components/omnibox/browser/shortcuts_provider_unittest.cc
+++ b/components/omnibox/browser/shortcuts_provider_unittest.cc
@@ -984,6 +984,10 @@
       create_shortcut_data("urls-saddling-searches", false, 3),
       create_shortcut_data("urls-saddling-searches", true, 2),
       create_shortcut_data("urls-saddling-searches", false, 1),
+      create_shortcut_data("boosting-both-url", false, 2),
+      create_shortcut_data("boosting-both-url", false, 1),
+      create_shortcut_data("boosting-both-search", true, 3),
+      create_shortcut_data("boosting-both-search", true, 2),
   };
 
   PopulateShortcutsBackendWithTestData(client_->GetShortcutsBackend(),
@@ -1151,6 +1155,64 @@
     EXPECT_LE(matches[2].relevance, kMaxUnboostedScore);
     EXPECT_TRUE(trigger_service->GetFeatureTriggeredInSession(trigger_feature));
   }
+
+  {
+    // Boost URLs according to URL params, not search params.
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        omnibox_feature_configs::ShortcutBoosting::kShortcutBoost,
+        {
+            {"ShortcutBoostUrlScore", "1300"},
+            {"ShortcutBoostSearchScore", "1310"},
+            {"ShortcutBoostNonTopHitThreshold", "2"},
+            {"ShortcutBoostNonTopHitSearchThreshold", "3"},
+        });
+    scoped_config.Reset();
+
+    trigger_service->ResetSession();
+    AutocompleteInput input(u"boosting-both-url",
+                            metrics::OmniboxEventProto::OTHER,
+                            TestSchemeClassifier());
+    provider_->Start(input, false);
+    const auto& matches = provider_->matches();
+    EXPECT_EQ(matches.size(), 2u);
+    EXPECT_EQ(matches[0].destination_url.spec(),
+              "https://boosting-both-url.com/2");
+    EXPECT_EQ(matches[1].destination_url.spec(),
+              "https://boosting-both-url.com/1");
+    EXPECT_EQ(matches[0].relevance, 1302);
+    EXPECT_LE(matches[1].relevance, kMaxUnboostedScore);
+    EXPECT_TRUE(trigger_service->GetFeatureTriggeredInSession(trigger_feature));
+  }
+
+  {
+    // Boost searches according to search params, not URL params.
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        omnibox_feature_configs::ShortcutBoosting::kShortcutBoost,
+        {
+            {"ShortcutBoostUrlScore", "1300"},
+            {"ShortcutBoostSearchScore", "1310"},
+            {"ShortcutBoostNonTopHitThreshold", "2"},
+            {"ShortcutBoostNonTopHitSearchThreshold", "3"},
+        });
+    scoped_config.Reset();
+
+    trigger_service->ResetSession();
+    AutocompleteInput input(u"boosting-both-search",
+                            metrics::OmniboxEventProto::OTHER,
+                            TestSchemeClassifier());
+    provider_->Start(input, false);
+    const auto& matches = provider_->matches();
+    EXPECT_EQ(matches.size(), 2u);
+    EXPECT_EQ(matches[0].destination_url.spec(),
+              "https://boosting-both-search.com/3");
+    EXPECT_EQ(matches[1].destination_url.spec(),
+              "https://boosting-both-search.com/2");
+    EXPECT_EQ(matches[0].relevance, 1313);
+    EXPECT_LE(matches[1].relevance, kMaxUnboostedScore);
+    EXPECT_TRUE(trigger_service->GetFeatureTriggeredInSession(trigger_feature));
+  }
 }
 
 #if !BUILDFLAG(IS_IOS)
diff --git a/components/omnibox/browser/url_index_private_data.cc b/components/omnibox/browser/url_index_private_data.cc
index 2c49bcb7..fdf0a67 100644
--- a/components/omnibox/browser/url_index_private_data.cc
+++ b/components/omnibox/browser/url_index_private_data.cc
@@ -812,6 +812,7 @@
   HistoryID history_id = static_cast<HistoryID>(row.id());
   // Split URL into individual, unique words then add in the title words.
   const GURL& gurl(row.url());
+  DCHECK(gurl.is_valid());
   const std::u16string& url = bookmarks::CleanUpUrlForMatching(gurl, nullptr);
   String16Set url_words = String16SetFromString16(
       url, word_starts ? &word_starts->url_word_starts_ : nullptr);
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index 6be7db4c..c452f351 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -426,6 +426,7 @@
     deps = [
       "//base",
       "//components/optimization_guide/proto:optimization_guide_proto",
+      "//google_apis",
       "//url",
     ]
   }
diff --git a/components/optimization_guide/core/model_execution/model_execution_manager.cc b/components/optimization_guide/core/model_execution/model_execution_manager.cc
index 38bd58b..b066d50 100644
--- a/components/optimization_guide/core/model_execution/model_execution_manager.cc
+++ b/components/optimization_guide/core/model_execution/model_execution_manager.cc
@@ -234,9 +234,14 @@
     return;
   }
 
-  if (execute_response->has_error_message()) {
-    scoped_logger.set_message(base::StringPrintf(
-        "Error: %s", execute_response->error_message().c_str()));
+  if (execute_response->has_error_response()) {
+    scoped_logger.set_message("Error: No Response Metadata");
+    std::move(callback).Run(
+        base::unexpected(
+            OptimizationGuideModelExecutionError::FromModelExecutionServerError(
+                execute_response->error_response())),
+        nullptr);
+    return;
   }
 
   if (!execute_response->has_response_metadata()) {
diff --git a/components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.cc b/components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.cc
index 54c0e5e..60bf6514 100644
--- a/components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.cc
+++ b/components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.cc
@@ -29,6 +29,35 @@
   }
 }
 
+//  static
+OptimizationGuideModelExecutionError
+OptimizationGuideModelExecutionError::FromModelExecutionServerError(
+    proto::ErrorResponse error) {
+  switch (error.error_state()) {
+    case proto::ErrorState::ERROR_STATE_UNSPECIFIED:
+      return OptimizationGuideModelExecutionError(
+          ModelExecutionError::kGenericFailure);
+    case proto::ErrorState::ERROR_STATE_INTERNAL_SERVER_ERROR_RETRY:
+      return OptimizationGuideModelExecutionError(
+          ModelExecutionError::kRetryableError);
+    case proto::ErrorState::ERROR_STATE_INTERNAL_SERVER_ERROR_NO_RETRY:
+      return OptimizationGuideModelExecutionError(
+          ModelExecutionError::kNonRetryableError);
+    case proto::ErrorState::ERROR_STATE_UNSUPPORTED_LANGUAGE:
+      return OptimizationGuideModelExecutionError(
+          ModelExecutionError::kUnsupportedLanguage);
+    case proto::ErrorState::ERROR_STATE_FILTERED:
+      return OptimizationGuideModelExecutionError(
+          ModelExecutionError::kFiltered);
+    case proto::ErrorState::ERROR_STATE_REQUEST_THROTTLED:
+      return OptimizationGuideModelExecutionError(
+          ModelExecutionError::kRequestThrottled);
+    case proto::ErrorState::ERROR_STATE_DISABLED:
+      return OptimizationGuideModelExecutionError(
+          ModelExecutionError::kDisabled);
+  }
+}
+
 // static
 OptimizationGuideModelExecutionError
 OptimizationGuideModelExecutionError::FromModelExecutionError(
@@ -49,9 +78,14 @@
   switch (error_) {
     case ModelExecutionError::kInvalidRequest:
     case ModelExecutionError::kPermissionDenied:
+    case ModelExecutionError::kNonRetryableError:
+    case ModelExecutionError::kUnsupportedLanguage:
+    case ModelExecutionError::kFiltered:
+    case ModelExecutionError::kDisabled:
       return false;
     case ModelExecutionError::kRequestThrottled:
     case ModelExecutionError::kGenericFailure:
+    case ModelExecutionError::kRetryableError:
       return true;
     case ModelExecutionError::kUnknown:
       NOTREACHED();
diff --git a/components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h b/components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h
index 9d642578..389f9f1 100644
--- a/components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h
+++ b/components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h
@@ -4,6 +4,7 @@
 #ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_OPTIMIZATION_GUIDE_MODEL_EXECUTION_ERROR_H_
 #define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_OPTIMIZATION_GUIDE_MODEL_EXECUTION_ERROR_H_
 
+#include "components/optimization_guide/proto/model_execution.pb.h"
 #include "net/http/http_status_code.h"
 
 namespace optimization_guide {
@@ -22,14 +23,27 @@
     kPermissionDenied,
     // Other generic failures.
     kGenericFailure,
+    // Retryable error occurred in server.
+    kRetryableError,
+    // Non-retryable error occurred in server.
+    kNonRetryableError,
+    // Unsupported language.
+    kUnsupportedLanguage,
+    // Request was filtered.
+    kFiltered,
+    // Response was disabled.
+    kDisabled,
 
     // Insert new values before this line.
-    kMaxValue = kGenericFailure
+    kMaxValue = kDisabled
   };
 
   static OptimizationGuideModelExecutionError FromHttpStatusCode(
       net::HttpStatusCode response_code);
 
+  static OptimizationGuideModelExecutionError FromModelExecutionServerError(
+      proto::ErrorResponse error);
+
   static OptimizationGuideModelExecutionError FromModelExecutionError(
       ModelExecutionError error);
 
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 49f831e..2fac85a 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -297,6 +297,11 @@
 #endif
 );
 
+// Kill switch for disabling model quality logging.
+BASE_FEATURE(kModelQualityLogging,
+             "ModelQualityLogging",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Enables fetching personalized metadata from Optimization Guide Service.
 BASE_FEATURE(kOptimizationGuidePersonalizedFetching,
              "OptimizationPersonalizedHintsFetching",
@@ -436,6 +441,29 @@
   return base::FeatureList::IsEnabled(kOptimizationHints);
 }
 
+bool IsModelQualityLoggingEnabled() {
+  return base::FeatureList::IsEnabled(kModelQualityLogging);
+}
+
+bool IsModelQualityLoggingEnabledForFeature(
+    proto::ModelExecutionFeature feature_name) {
+  if (!IsModelQualityLoggingEnabled()) {
+    return false;
+  }
+
+  std::string param_name =
+      base::ToLowerASCII(proto::ModelExecutionFeature_Name(feature_name));
+  bool default_value = true;
+
+  // Disable compose feature by default.
+  if (feature_name ==
+      proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_COMPOSE) {
+    default_value = false;
+  }
+  return GetFieldTrialParamByFeatureAsBool(kModelQualityLogging, param_name,
+                                           default_value);
+}
+
 bool IsRemoteFetchingEnabled() {
   return base::FeatureList::IsEnabled(kRemoteOptimizationGuideFetching);
 }
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index 846dbfe..a94ac331 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -14,6 +14,7 @@
 #include "base/time/time.h"
 #include "components/optimization_guide/core/page_content_annotation_type.h"
 #include "components/optimization_guide/proto/hints.pb.h"
+#include "components/optimization_guide/proto/model_execution.pb.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "net/nqe/effective_connection_type.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -55,6 +56,7 @@
 BASE_DECLARE_FEATURE(kOptimizationGuidePredictionModelKillswitch);
 BASE_DECLARE_FEATURE(kOptimizationGuideModelExecution);
 BASE_DECLARE_FEATURE(kOptimizationGuideOnDeviceModel);
+BASE_DECLARE_FEATURE(kModelQualityLogging);
 
 // Enables use of task runner with trait CONTINUE_ON_SHUTDOWN for page content
 // annotations on-device models.
@@ -354,6 +356,13 @@
 // Returns whether to query text embeddings coming from history service.
 bool ShouldQueryEmbeddings();
 
+// Whether logging of model quality is enabled.
+bool IsModelQualityLoggingEnabled();
+
+// Whether model quality logging is enabled for a feature.
+bool IsModelQualityLoggingEnabledForFeature(
+    proto::ModelExecutionFeature feature);
+
 // Returns whether the `model_version` for `opt_target` is part of emergency
 // killswitch, and this model should be stopped serving immediately.
 std::map<proto::OptimizationTarget, std::set<int64_t>>
diff --git a/components/optimization_guide/core/optimization_guide_features_unittest.cc b/components/optimization_guide/core/optimization_guide_features_unittest.cc
index e898aa2..05fdba91 100644
--- a/components/optimization_guide/core/optimization_guide_features_unittest.cc
+++ b/components/optimization_guide/core/optimization_guide_features_unittest.cc
@@ -94,6 +94,58 @@
   EXPECT_TRUE(features::ShouldExecutePageEntitiesModelOnPageContent("en-US"));
 }
 
+TEST(OptimizationGuideFeaturesTest, ModelQualityLoggingDefault) {
+  base::test::ScopedFeatureList scoped_feature_list;
+
+  scoped_feature_list.InitAndEnableFeature(features::kModelQualityLogging);
+
+  // By default compose feature should be disabled.
+  EXPECT_TRUE(features::IsModelQualityLoggingEnabled());
+  EXPECT_FALSE(features::IsModelQualityLoggingEnabledForFeature(
+      proto::MODEL_EXECUTION_FEATURE_COMPOSE));
+
+  // Both wallpaper search and tab organization should be enabled by default.
+  EXPECT_TRUE(features::IsModelQualityLoggingEnabledForFeature(
+      proto::MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION));
+  EXPECT_TRUE(features::IsModelQualityLoggingEnabledForFeature(
+      proto::MODEL_EXECUTION_FEATURE_WALLPAPER_SEARCH));
+}
+
+TEST(OptimizationGuideFeaturesTest, ComposeModelQualityLoggingEnabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      features::kModelQualityLogging,
+      {{"model_execution_feature_compose", "true"},
+       {"model_execution_feature_wallpaper_search", "false"},
+       {"model_execution_feature_tab_organization", "false"}});
+
+  EXPECT_TRUE(features::IsModelQualityLoggingEnabled());
+  EXPECT_TRUE(features::IsModelQualityLoggingEnabledForFeature(
+      proto::MODEL_EXECUTION_FEATURE_COMPOSE));
+
+  // Both wallpaper search and tab organization should be disabled.
+  EXPECT_FALSE(features::IsModelQualityLoggingEnabledForFeature(
+      proto::MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION));
+  EXPECT_FALSE(features::IsModelQualityLoggingEnabledForFeature(
+      proto::MODEL_EXECUTION_FEATURE_WALLPAPER_SEARCH));
+}
+
+TEST(OptimizationGuideFeaturesTest, ModelQualityLoggingDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+
+  scoped_feature_list.InitAndDisableFeature(features::kModelQualityLogging);
+
+  // All features logging should be disabled if ModelQualityLogging is disabled.
+  EXPECT_FALSE(features::IsModelQualityLoggingEnabled());
+  EXPECT_FALSE(features::IsModelQualityLoggingEnabledForFeature(
+      proto::MODEL_EXECUTION_FEATURE_COMPOSE));
+  EXPECT_FALSE(features::IsModelQualityLoggingEnabledForFeature(
+      proto::MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION));
+  EXPECT_FALSE(features::IsModelQualityLoggingEnabledForFeature(
+      proto::MODEL_EXECUTION_FEATURE_WALLPAPER_SEARCH));
+}
+
 TEST(OptimizationGuideFeaturesTest,
      ShouldExecutePageEntitiesModelOnPageContentWithAllowlist) {
   base::test::ScopedFeatureList scoped_feature_list;
diff --git a/components/optimization_guide/core/optimization_guide_switches.cc b/components/optimization_guide/core/optimization_guide_switches.cc
index c34834ce..0da763b 100644
--- a/components/optimization_guide/core/optimization_guide_switches.cc
+++ b/components/optimization_guide/core/optimization_guide_switches.cc
@@ -11,6 +11,7 @@
 #include "base/strings/string_split.h"
 #include "build/build_config.h"
 #include "components/optimization_guide/proto/hints.pb.h"
+#include "google_apis/google_api_keys.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace optimization_guide {
@@ -113,6 +114,23 @@
 const char kPageContentAnnotationsValidationWriteToFile[] =
     "page-content-annotations-validation-write-to-file";
 
+// Overrides the model quality service URL.
+const char kModelQualityServiceURL[] = "model-quality-service-url";
+
+// Overrides the ModelQuality Service API Key for remote requests to be made.
+const char kModelQualityServiceAPIKey[] = "model-quality-service-api-key";
+
+std::string GetModelQualityServiceAPIKey() {
+  // Command line override takes priority.
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(switches::kModelQualityServiceAPIKey)) {
+    return command_line->GetSwitchValueASCII(
+        switches::kModelQualityServiceAPIKey);
+  }
+
+  return google_apis::GetAPIKey();
+}
+
 bool IsHintComponentProcessingDisabled() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(kHintsProtoOverride);
 }
diff --git a/components/optimization_guide/core/optimization_guide_switches.h b/components/optimization_guide/core/optimization_guide_switches.h
index 2dc7658..fee6224 100644
--- a/components/optimization_guide/core/optimization_guide_switches.h
+++ b/components/optimization_guide/core/optimization_guide_switches.h
@@ -45,6 +45,11 @@
 extern const char kPageContentAnnotationsValidationContentVisibility[];
 extern const char kPageContentAnnotationsValidationTextEmbedding[];
 extern const char kPageContentAnnotationsValidationWriteToFile[];
+extern const char kModelQualityServiceURL[];
+extern const char kModelQualityServiceAPIKey[];
+
+// The API key for the ModelQualityLoggingService.
+std::string GetModelQualityServiceAPIKey();
 
 // Returns whether the hint component should be processed.
 // Available hint components are only processed if a proto override isn't being
diff --git a/components/optimization_guide/proto/model_execution.proto b/components/optimization_guide/proto/model_execution.proto
index b5be8a1..19ec557e 100644
--- a/components/optimization_guide/proto/model_execution.proto
+++ b/components/optimization_guide/proto/model_execution.proto
@@ -21,17 +21,34 @@
 }
 
 message ExecuteResponse {
+  reserved 3;
+
   optional int64 model_version = 1;
 
   oneof response {
     // The metadata for the response returned for the feature.
     Any response_metadata = 2;
-    string error_message = 3;
+    ErrorResponse error_response = 5;
   }
 
   optional string server_execution_id = 4;
 }
 
+message ErrorResponse {
+  optional ErrorState error_state = 1;
+}
+
+// Possible failure modes of the service
+enum ErrorState {
+  ERROR_STATE_UNSPECIFIED = 0;
+  ERROR_STATE_INTERNAL_SERVER_ERROR_RETRY = 1;
+  ERROR_STATE_INTERNAL_SERVER_ERROR_NO_RETRY = 2;
+  ERROR_STATE_UNSUPPORTED_LANGUAGE = 3;
+  ERROR_STATE_FILTERED = 4;
+  ERROR_STATE_REQUEST_THROTTLED = 5;
+  ERROR_STATE_DISABLED = 6;
+}
+
 enum ModelExecutionFeature {
   MODEL_EXECUTION_FEATURE_UNSPECIFIED = 0;
   MODEL_EXECUTION_FEATURE_COMPOSE = 1;
diff --git a/components/page_load_metrics/browser/fake_page_load_metrics_observer_delegate.cc b/components/page_load_metrics/browser/fake_page_load_metrics_observer_delegate.cc
index 4a20435..f8e1a34 100644
--- a/components/page_load_metrics/browser/fake_page_load_metrics_observer_delegate.cc
+++ b/components/page_load_metrics/browser/fake_page_load_metrics_observer_delegate.cc
@@ -145,15 +145,15 @@
   return normalized_cls_data_;
 }
 
-const NormalizedResponsivenessMetrics&
-FakePageLoadMetricsObserverDelegate::GetNormalizedResponsivenessMetrics()
+const ResponsivenessMetricsNormalization&
+FakePageLoadMetricsObserverDelegate::GetResponsivenessMetricsNormalization()
     const {
-  return normalized_responsiveness_metrics_;
+  return responsiveness_metrics_normalization_;
 }
 
-const NormalizedResponsivenessMetrics& FakePageLoadMetricsObserverDelegate::
-    GetSoftNavigationIntervalNormalizedResponsivenessMetrics() const {
-  return normalized_responsiveness_metrics_;
+const ResponsivenessMetricsNormalization& FakePageLoadMetricsObserverDelegate::
+    GetSoftNavigationIntervalResponsivenessMetricsNormalization() const {
+  return responsiveness_metrics_normalization_;
 }
 
 const mojom::InputTiming&
diff --git a/components/page_load_metrics/browser/fake_page_load_metrics_observer_delegate.h b/components/page_load_metrics/browser/fake_page_load_metrics_observer_delegate.h
index 4cbecb7..fa2caa6 100644
--- a/components/page_load_metrics/browser/fake_page_load_metrics_observer_delegate.h
+++ b/components/page_load_metrics/browser/fake_page_load_metrics_observer_delegate.h
@@ -62,10 +62,10 @@
       BfcacheStrategy bfcache_strategy) const override;
   const NormalizedCLSData& GetSoftNavigationIntervalNormalizedCLSData()
       const override;
-  const NormalizedResponsivenessMetrics& GetNormalizedResponsivenessMetrics()
-      const override;
-  const NormalizedResponsivenessMetrics&
-  GetSoftNavigationIntervalNormalizedResponsivenessMetrics() const override;
+  const ResponsivenessMetricsNormalization&
+  GetResponsivenessMetricsNormalization() const override;
+  const ResponsivenessMetricsNormalization&
+  GetSoftNavigationIntervalResponsivenessMetricsNormalization() const override;
   const mojom::InputTiming& GetPageInputTiming() const override;
   const absl::optional<blink::SubresourceLoadMetrics>&
   GetSubresourceLoadMetrics() const override;
@@ -103,7 +103,7 @@
   mojom::FrameMetadata subframe_metadata_;
   PageRenderData page_render_data_;
   NormalizedCLSData normalized_cls_data_;
-  NormalizedResponsivenessMetrics normalized_responsiveness_metrics_;
+  ResponsivenessMetricsNormalization responsiveness_metrics_normalization_;
   mojom::InputTiming page_input_timing_;
   absl::optional<blink::SubresourceLoadMetrics> subresource_load_metrics_;
   PageRenderData main_frame_render_data_;
diff --git a/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.cc
index 1905ab3..5b7669c 100644
--- a/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/back_forward_cache_page_load_metrics_observer.cc
@@ -335,38 +335,37 @@
   if (!has_ever_entered_back_forward_cache_)
     return;
   // Normalized Responsiveness Metrics.
-  const page_load_metrics::NormalizedResponsivenessMetrics&
-      normalized_responsiveness_metrics =
-          GetDelegate().GetNormalizedResponsivenessMetrics();
+  const page_load_metrics::ResponsivenessMetricsNormalization&
+      responsiveness_metrics_normalization =
+          GetDelegate().GetResponsivenessMetricsNormalization();
 
-  if (!normalized_responsiveness_metrics.num_user_interactions)
+  if (!responsiveness_metrics_normalization.num_user_interactions()) {
     return;
+  }
 
-  auto& max_event_durations =
-      normalized_responsiveness_metrics.normalized_max_event_durations;
   // HistoryNavigation is a singular event, and we share the same instance as
   // long as we use the same source ID.
   ukm::builders::HistoryNavigation builder(
       GetLastUkmSourceIdForBackForwardCacheRestore());
   builder
       .SetWorstUserInteractionLatencyAfterBackForwardCacheRestore_MaxEventDuration2(
-          max_event_durations.worst_latency.InMilliseconds());
+          responsiveness_metrics_normalization.worst_latency()
+              .value()
+              .InMilliseconds());
   UmaHistogramCustomTimes(
       internal::
           kWorstUserInteractionLatency_MaxEventDuration_AfterBackForwardCacheRestore,
-      max_event_durations.worst_latency, base::Milliseconds(1),
-      base::Seconds(60), 50);
+      responsiveness_metrics_normalization.worst_latency().value(),
+      base::Milliseconds(1), base::Seconds(60), 50);
 
-  base::TimeDelta high_percentile2_max_event_duration = page_load_metrics::
-      ResponsivenessMetricsNormalization::ApproximateHighPercentile(
-          normalized_responsiveness_metrics.num_user_interactions,
-          max_event_durations.worst_ten_latencies);
+  base::TimeDelta high_percentile2_max_event_duration =
+      responsiveness_metrics_normalization.ApproximateHighPercentile().value();
   builder
       .SetUserInteractionLatencyAfterBackForwardCacheRestore_HighPercentile2_MaxEventDuration(
           high_percentile2_max_event_duration.InMilliseconds());
   builder.SetNumInteractionsAfterBackForwardCacheRestore(
       ukm::GetExponentialBucketMinForCounts1000(
-          normalized_responsiveness_metrics.num_user_interactions));
+          responsiveness_metrics_normalization.num_user_interactions()));
 
   UmaHistogramCustomTimes(
       internal::
@@ -375,7 +374,7 @@
       base::Seconds(60), 50);
   base::UmaHistogramCounts1000(
       internal::kNumInteractions_AfterBackForwardCacheRestore,
-      normalized_responsiveness_metrics.num_user_interactions);
+      responsiveness_metrics_normalization.num_user_interactions());
 
   builder.Record(ukm::UkmRecorder::Get());
 }
diff --git a/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.cc
index 1de0250..d43ed1e6 100644
--- a/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/core/uma_page_load_metrics_observer.cc
@@ -898,28 +898,24 @@
 }
 
 void UmaPageLoadMetricsObserver::RecordNormalizedResponsivenessMetrics() {
-  const page_load_metrics::NormalizedResponsivenessMetrics&
-      normalized_responsiveness_metrics =
-          GetDelegate().GetNormalizedResponsivenessMetrics();
-  if (!normalized_responsiveness_metrics.num_user_interactions)
+  const page_load_metrics::ResponsivenessMetricsNormalization&
+      responsiveness_metrics_normalization =
+          GetDelegate().GetResponsivenessMetricsNormalization();
+  if (!responsiveness_metrics_normalization.num_user_interactions()) {
     return;
-  auto& max_event_durations =
-      normalized_responsiveness_metrics.normalized_max_event_durations;
+  }
 
   UmaHistogramCustomTimes(
       internal::kHistogramWorstUserInteractionLatencyMaxEventDuration,
-      max_event_durations.worst_latency, base::Milliseconds(1),
-      base::Seconds(60), 50);
+      responsiveness_metrics_normalization.worst_latency().value(),
+      base::Milliseconds(1), base::Seconds(60), 50);
   UmaHistogramCustomTimes(
       internal::kHistogramUserInteractionLatencyHighPercentile2MaxEventDuration,
-      page_load_metrics::ResponsivenessMetricsNormalization::
-          ApproximateHighPercentile(
-              normalized_responsiveness_metrics.num_user_interactions,
-              max_event_durations.worst_ten_latencies),
+      responsiveness_metrics_normalization.ApproximateHighPercentile().value(),
       base::Milliseconds(1), base::Seconds(60), 50);
   base::UmaHistogramCounts1000(
       internal::kHistogramNumInteractions,
-      normalized_responsiveness_metrics.num_user_interactions);
+      responsiveness_metrics_normalization.num_user_interactions());
 }
 
 void UmaPageLoadMetricsObserver::RecordForegroundDurationHistograms(
diff --git a/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc
index b08aa1f..b4d118bb 100644
--- a/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc
@@ -399,25 +399,20 @@
 
   DCHECK(GetDelegate().WasPrerenderedThenActivatedInForeground());
 
-  const page_load_metrics::NormalizedResponsivenessMetrics&
-      normalized_responsiveness_metrics =
-          GetDelegate().GetNormalizedResponsivenessMetrics();
-  if (!normalized_responsiveness_metrics.num_user_interactions) {
+  const page_load_metrics::ResponsivenessMetricsNormalization&
+      responsiveness_metrics_normalization =
+          GetDelegate().GetResponsivenessMetricsNormalization();
+  if (!responsiveness_metrics_normalization.num_user_interactions()) {
     return;
   }
 
-  const page_load_metrics::NormalizedInteractionLatencies& max_event_durations =
-      normalized_responsiveness_metrics.normalized_max_event_durations;
-
-  base::TimeDelta high_percentile2_max_event_duration = page_load_metrics::
-      ResponsivenessMetricsNormalization::ApproximateHighPercentile(
-          normalized_responsiveness_metrics.num_user_interactions,
-          max_event_durations.worst_ten_latencies);
+  base::TimeDelta high_percentile2_max_event_duration =
+      responsiveness_metrics_normalization.ApproximateHighPercentile().value();
 
   UmaHistogramCustomTimes(
       internal::kHistogramPrerenderWorstUserInteractionLatencyMaxEventDuration,
-      max_event_durations.worst_latency, base::Milliseconds(1),
-      base::Seconds(60), 50);
+      responsiveness_metrics_normalization.worst_latency().value(),
+      base::Milliseconds(1), base::Seconds(60), 50);
   UmaHistogramCustomTimes(
       internal::
           kHistogramPrerenderUserInteractionLatencyHighPercentile2MaxEventDuration,
@@ -425,18 +420,20 @@
       base::Seconds(60), 50);
   base::UmaHistogramCounts1000(
       internal::kHistogramPrerenderNumInteractions,
-      normalized_responsiveness_metrics.num_user_interactions);
+      responsiveness_metrics_normalization.num_user_interactions());
 
   ukm::builders::PrerenderPageLoad builder(GetDelegate().GetPageUkmSourceId());
   builder.SetInteractiveTiming_WorstUserInteractionLatency_MaxEventDuration(
-      max_event_durations.worst_latency.InMilliseconds());
+      responsiveness_metrics_normalization.worst_latency()
+          .value()
+          .InMilliseconds());
 
   builder
       .SetInteractiveTiming_UserInteractionLatency_HighPercentile2_MaxEventDuration(
           high_percentile2_max_event_duration.InMilliseconds());
   builder.SetInteractiveTiming_NumInteractions(
       ukm::GetExponentialBucketMinForCounts1000(
-          normalized_responsiveness_metrics.num_user_interactions));
+          responsiveness_metrics_normalization.num_user_interactions()));
 
   builder.Record(ukm::UkmRecorder::Get());
 }
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h b/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h
index f780c32..cd988e9 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h
+++ b/components/page_load_metrics/browser/page_load_metrics_observer_delegate.h
@@ -185,14 +185,13 @@
       BfcacheStrategy bfcache_strategy) const = 0;
   virtual const NormalizedCLSData& GetSoftNavigationIntervalNormalizedCLSData()
       const = 0;
-  // Returns normalized responsiveness metrics data. Currently we normalize
-  // user interaction latencies from all renderer frames in a few different
-  // ways.
-  virtual const NormalizedResponsivenessMetrics&
-  GetNormalizedResponsivenessMetrics() const = 0;
+  // Returns normalized responsiveness metrics data. Normalization explained in
+  // https://web.dev/inp.
+  virtual const ResponsivenessMetricsNormalization&
+  GetResponsivenessMetricsNormalization() const = 0;
 
-  virtual const NormalizedResponsivenessMetrics&
-  GetSoftNavigationIntervalNormalizedResponsivenessMetrics() const = 0;
+  virtual const ResponsivenessMetricsNormalization&
+  GetSoftNavigationIntervalResponsivenessMetricsNormalization() const = 0;
 
   // InputTiming data accumulated across all frames.
   virtual const mojom::InputTiming& GetPageInputTiming() const = 0;
diff --git a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
index 00ebda9..28a6d8c4 100644
--- a/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
+++ b/components/page_load_metrics/browser/page_load_metrics_update_dispatcher.h
@@ -218,16 +218,14 @@
                ? layout_shift_normalization_for_bfcache_.normalized_cls_data()
                : layout_shift_normalization_.normalized_cls_data();
   }
-  const NormalizedResponsivenessMetrics& normalized_responsiveness_metrics()
-      const {
-    return responsiveness_metrics_normalization_
-        .GetNormalizedResponsivenessMetrics();
+  const ResponsivenessMetricsNormalization&
+  responsiveness_metrics_normalization() const {
+    return responsiveness_metrics_normalization_;
   }
 
-  const NormalizedResponsivenessMetrics&
-  soft_navigation_interval_normalized_responsiveness_metrics() const {
-    return soft_navigation_interval_responsiveness_metrics_normalization_
-        .GetNormalizedResponsivenessMetrics();
+  const ResponsivenessMetricsNormalization&
+  soft_navigation_interval_responsiveness_metrics_normalization() const {
+    return soft_navigation_interval_responsiveness_metrics_normalization_;
   }
 
   const NormalizedCLSData& soft_navigation_interval_normalized_layout_shift()
@@ -235,7 +233,7 @@
     return soft_nav_interval_layout_shift_normalization_.normalized_cls_data();
   }
 
-  void ResetSoftNavigationIntervalNormalizedResponsivenessMetrics() {
+  void ResetSoftNavigationIntervalResponsivenessMetricsNormalization() {
     soft_navigation_interval_responsiveness_metrics_normalization_
         .ClearAllUserInteractionLatencies();
   }
diff --git a/components/page_load_metrics/browser/page_load_tracker.cc b/components/page_load_metrics/browser/page_load_tracker.cc
index 22bb54b8..0fd8a95 100644
--- a/components/page_load_metrics/browser/page_load_tracker.cc
+++ b/components/page_load_metrics/browser/page_load_tracker.cc
@@ -1134,7 +1134,7 @@
   // when a new soft nav comes in.
   if (new_soft_navigation_metrics.count > soft_navigation_metrics_->count) {
     metrics_update_dispatcher_
-        .ResetSoftNavigationIntervalNormalizedResponsivenessMetrics();
+        .ResetSoftNavigationIntervalResponsivenessMetricsNormalization();
     metrics_update_dispatcher_.ResetSoftNavigationIntervalLayoutShift();
   }
 
@@ -1325,16 +1325,16 @@
       .soft_navigation_interval_normalized_layout_shift();
 }
 
-const NormalizedResponsivenessMetrics&
-PageLoadTracker::GetNormalizedResponsivenessMetrics() const {
-  return metrics_update_dispatcher_.normalized_responsiveness_metrics();
+const ResponsivenessMetricsNormalization&
+PageLoadTracker::GetResponsivenessMetricsNormalization() const {
+  return metrics_update_dispatcher_.responsiveness_metrics_normalization();
 }
 
-const NormalizedResponsivenessMetrics&
-PageLoadTracker::GetSoftNavigationIntervalNormalizedResponsivenessMetrics()
+const ResponsivenessMetricsNormalization&
+PageLoadTracker::GetSoftNavigationIntervalResponsivenessMetricsNormalization()
     const {
   return metrics_update_dispatcher_
-      .soft_navigation_interval_normalized_responsiveness_metrics();
+      .soft_navigation_interval_responsiveness_metrics_normalization();
 }
 
 const mojom::InputTiming& PageLoadTracker::GetPageInputTiming() const {
diff --git a/components/page_load_metrics/browser/page_load_tracker.h b/components/page_load_metrics/browser/page_load_tracker.h
index fad6912..08e0865 100644
--- a/components/page_load_metrics/browser/page_load_tracker.h
+++ b/components/page_load_metrics/browser/page_load_tracker.h
@@ -287,10 +287,10 @@
       BfcacheStrategy bfcache_strategy) const override;
   const NormalizedCLSData& GetSoftNavigationIntervalNormalizedCLSData()
       const override;
-  const NormalizedResponsivenessMetrics& GetNormalizedResponsivenessMetrics()
-      const override;
-  const NormalizedResponsivenessMetrics&
-  GetSoftNavigationIntervalNormalizedResponsivenessMetrics() const override;
+  const ResponsivenessMetricsNormalization&
+  GetResponsivenessMetricsNormalization() const override;
+  const ResponsivenessMetricsNormalization&
+  GetSoftNavigationIntervalResponsivenessMetricsNormalization() const override;
   const mojom::InputTiming& GetPageInputTiming() const override;
   const absl::optional<blink::SubresourceLoadMetrics>&
   GetSubresourceLoadMetrics() const override;
diff --git a/components/page_load_metrics/browser/responsiveness_metrics_normalization.cc b/components/page_load_metrics/browser/responsiveness_metrics_normalization.cc
index 8382156..e42876c 100644
--- a/components/page_load_metrics/browser/responsiveness_metrics_normalization.cc
+++ b/components/page_load_metrics/browser/responsiveness_metrics_normalization.cc
@@ -6,66 +6,66 @@
 
 namespace page_load_metrics {
 
-NormalizedInteractionLatencies::NormalizedInteractionLatencies() = default;
-NormalizedInteractionLatencies::~NormalizedInteractionLatencies() = default;
-
-NormalizedResponsivenessMetrics::NormalizedResponsivenessMetrics() = default;
-NormalizedResponsivenessMetrics::~NormalizedResponsivenessMetrics() = default;
-
 ResponsivenessMetricsNormalization::ResponsivenessMetricsNormalization() =
     default;
 ResponsivenessMetricsNormalization::~ResponsivenessMetricsNormalization() =
     default;
 
-// static
-base::TimeDelta ResponsivenessMetricsNormalization::ApproximateHighPercentile(
-    uint64_t num_interactions,
-    std::priority_queue<base::TimeDelta,
-                        std::vector<base::TimeDelta>,
-                        std::greater<>> worst_ten_latencies) {
-  DCHECK(num_interactions);
-  int index = std::max(0, static_cast<int>(worst_ten_latencies.size()) - 1 -
-                              static_cast<int>(num_interactions /
-                                               kHighPercentileUpdateFrequency));
-  for (; index > 0; index--) {
-    worst_ten_latencies.pop();
+absl::optional<base::TimeDelta>
+ResponsivenessMetricsNormalization::ApproximateHighPercentile() const {
+  absl::optional<base::TimeDelta> approximate_high_percentile;
+  if (worst_ten_latencies_.size()) {
+    uint64_t index =
+        std::min(static_cast<uint64_t>(worst_ten_latencies_.size() - 1),
+                 static_cast<uint64_t>(num_user_interactions_ /
+                                       kHighPercentileUpdateFrequency));
+    approximate_high_percentile = worst_ten_latencies_[index];
   }
+  return approximate_high_percentile;
+}
 
-  return worst_ten_latencies.top();
+absl::optional<base::TimeDelta>
+ResponsivenessMetricsNormalization::worst_latency() const {
+  absl::optional<base::TimeDelta> worst_latency;
+  if (worst_ten_latencies_.size()) {
+    worst_latency = worst_ten_latencies_[0];
+  }
+  return worst_latency;
 }
 
 void ResponsivenessMetricsNormalization::AddNewUserInteractionLatencies(
     uint64_t num_new_interactions,
     const mojom::UserInteractionLatencies& max_event_durations) {
-  uint64_t last_num_user_interactions =
-      normalized_responsiveness_metrics_.num_user_interactions;
-  normalized_responsiveness_metrics_.num_user_interactions +=
-      num_new_interactions;
-  DCHECK(max_event_durations.is_user_interaction_latencies() ||
-         max_event_durations.is_worst_interaction_latency());
+  num_user_interactions_ += num_new_interactions;
   // Normalize max event durations.
-  NormalizeUserInteractionLatencies(
-      max_event_durations,
-      normalized_responsiveness_metrics_.normalized_max_event_durations,
-      last_num_user_interactions,
-      normalized_responsiveness_metrics_.num_user_interactions);
+  NormalizeUserInteractionLatencies(max_event_durations);
+}
+
+void ResponsivenessMetricsNormalization::ClearAllUserInteractionLatencies() {
+  num_user_interactions_ = 0;
+  worst_ten_latencies_ = std::vector<base::TimeDelta>();
 }
 
 void ResponsivenessMetricsNormalization::NormalizeUserInteractionLatencies(
-    const mojom::UserInteractionLatencies& user_interaction_latencies,
-    NormalizedInteractionLatencies& normalized_event_durations,
-    uint64_t last_num_user_interactions,
-    uint64_t current_num_user_interactions) {
+    const mojom::UserInteractionLatencies& user_interaction_latencies) {
   DCHECK(user_interaction_latencies.is_user_interaction_latencies());
+
+  // Insert each latency into the list if it is one of the worst ten seen so
+  // far. Use inplace_merge to keep the list sorted after appending an element.
   for (const mojom::UserInteractionLatencyPtr& user_interaction :
        user_interaction_latencies.get_user_interaction_latencies()) {
-    normalized_event_durations.worst_latency =
-        std::max(normalized_event_durations.worst_latency,
-                 user_interaction->interaction_latency);
-    normalized_event_durations.worst_ten_latencies.push(
-        user_interaction->interaction_latency);
-    if (normalized_event_durations.worst_ten_latencies.size() == 11) {
-      normalized_event_durations.worst_ten_latencies.pop();
+    if (worst_ten_latencies_.size() < 10) {
+      worst_ten_latencies_.push_back(user_interaction->interaction_latency);
+    } else if (user_interaction->interaction_latency >
+               worst_ten_latencies_.back()) {
+      worst_ten_latencies_.back() = user_interaction->interaction_latency;
+    } else {
+      continue;
+    }
+    if (worst_ten_latencies_.size() > 1) {
+      std::inplace_merge(worst_ten_latencies_.begin(),
+                         std::prev(worst_ten_latencies_.end()),
+                         worst_ten_latencies_.end(), std::greater<>());
     }
   }
 }
diff --git a/components/page_load_metrics/browser/responsiveness_metrics_normalization.h b/components/page_load_metrics/browser/responsiveness_metrics_normalization.h
index b36f2b3..a739936 100644
--- a/components/page_load_metrics/browser/responsiveness_metrics_normalization.h
+++ b/components/page_load_metrics/browser/responsiveness_metrics_normalization.h
@@ -14,31 +14,6 @@
 namespace page_load_metrics {
 
 constexpr uint64_t kHighPercentileUpdateFrequency = 50;
-// The struct that stores normalized user interactions latencies.
-struct NormalizedInteractionLatencies {
-  NormalizedInteractionLatencies();
-  ~NormalizedInteractionLatencies();
-
-  // The maximum value of user interaction latencies.
-  base::TimeDelta worst_latency;
-
-  // A min priority queue. The top is the smallest base::TimeDelta in the queue.
-  // We use the worst 10 latencies to approximate a high percentile.
-  std::priority_queue<base::TimeDelta,
-                      std::vector<base::TimeDelta>,
-                      std::greater<>>
-      worst_ten_latencies;
-};
-
-// The struct that stores all normalization results for a page load.
-struct NormalizedResponsivenessMetrics {
-  NormalizedResponsivenessMetrics();
-  ~NormalizedResponsivenessMetrics();
-  uint64_t num_user_interactions = 0;
-  // Max event duration
-  NormalizedInteractionLatencies normalized_max_event_durations;
-};
-
 // ResponsivenessMetricsNormalization implements some experimental normalization
 // strategies for responsiveness metrics. We aggregate user interaction latency
 // data from all renderer frames and calculate a score per page load.
@@ -56,28 +31,23 @@
       uint64_t num_new_interactions,
       const mojom::UserInteractionLatencies& max_event_durations);
 
-  const NormalizedResponsivenessMetrics& GetNormalizedResponsivenessMetrics()
-      const {
-    return normalized_responsiveness_metrics_;
-  }
-  void ClearAllUserInteractionLatencies() {
-    normalized_responsiveness_metrics_ = NormalizedResponsivenessMetrics();
-  }
+  void ClearAllUserInteractionLatencies();
 
   // Approximate a high percentile of user interaction latency.
-  static base::TimeDelta ApproximateHighPercentile(
-      uint64_t num_interactions,
-      std::priority_queue<base::TimeDelta,
-                          std::vector<base::TimeDelta>,
-                          std::greater<>> worst_ten_latencies);
+  absl::optional<base::TimeDelta> ApproximateHighPercentile() const;
+
+  uint64_t num_user_interactions() const { return num_user_interactions_; }
+
+  absl::optional<base::TimeDelta> worst_latency() const;
 
  private:
   void NormalizeUserInteractionLatencies(
-      const mojom::UserInteractionLatencies& user_interaction_latencies,
-      NormalizedInteractionLatencies& normalized_event_durations,
-      uint64_t last_num_user_interactions,
-      uint64_t current_num_user_interactions);
-  NormalizedResponsivenessMetrics normalized_responsiveness_metrics_;
+      const mojom::UserInteractionLatencies& user_interaction_latencies);
+
+  // A sorted list of the worst ten latencies, used to approximate a high
+  // percentile.
+  std::vector<base::TimeDelta> worst_ten_latencies_;
+  uint64_t num_user_interactions_ = 0;
 };
 
 }  // namespace page_load_metrics
diff --git a/components/page_load_metrics/browser/responsiveness_metrics_normalization_unittest.cc b/components/page_load_metrics/browser/responsiveness_metrics_normalization_unittest.cc
index 5263aa3..f35f13d 100644
--- a/components/page_load_metrics/browser/responsiveness_metrics_normalization_unittest.cc
+++ b/components/page_load_metrics/browser/responsiveness_metrics_normalization_unittest.cc
@@ -24,10 +24,17 @@
         num_new_interactions, max_event_durations);
   }
 
-  const page_load_metrics::NormalizedResponsivenessMetrics&
-  normalized_responsiveness_metrics() const {
-    return responsiveness_metrics_normalization_
-        .GetNormalizedResponsivenessMetrics();
+  uint64_t GetNumInteractions() {
+    return responsiveness_metrics_normalization_.num_user_interactions();
+  }
+
+  base::TimeDelta GetWorstInteraction() {
+    return responsiveness_metrics_normalization_.worst_latency().value();
+  }
+
+  base::TimeDelta GetHighPercentileInteraction() {
+    return responsiveness_metrics_normalization_.ApproximateHighPercentile()
+        .value();
   }
 
  private:
@@ -36,30 +43,63 @@
 };
 
 TEST_F(ResponsivenessMetricsNormalizationTest, SendAllInteractions) {
-  UserInteractionLatenciesPtr max_event_durations =
+  // Check that we get the correct count, worst, and high percentile
+  // with 3 interactions.
+  UserInteractionLatenciesPtr user_interaction_latencies_ptr =
       UserInteractionLatencies::NewUserInteractionLatencies({});
-  auto& user_interaction_latencies1 =
-      max_event_durations->get_user_interaction_latencies();
-  user_interaction_latencies1.emplace_back(UserInteractionLatency::New(
-      base::Milliseconds(50), UserInteractionType::kKeyboard));
-  user_interaction_latencies1.emplace_back(UserInteractionLatency::New(
-      base::Milliseconds(100), UserInteractionType::kTapOrClick));
-  user_interaction_latencies1.emplace_back(UserInteractionLatency::New(
-      base::Milliseconds(150), UserInteractionType::kDrag));
+  auto& user_interaction_latencies =
+      user_interaction_latencies_ptr->get_user_interaction_latencies();
+  user_interaction_latencies.emplace_back(UserInteractionLatency::New(
+      base::Milliseconds(3000), UserInteractionType::kTapOrClick));
+  user_interaction_latencies.emplace_back(UserInteractionLatency::New(
+      base::Milliseconds(3500), UserInteractionType::kTapOrClick));
+  user_interaction_latencies.emplace_back(UserInteractionLatency::New(
+      base::Milliseconds(2000), UserInteractionType::kTapOrClick));
+  AddNewUserInteractions(3, *user_interaction_latencies_ptr);
+  EXPECT_EQ(GetNumInteractions(), 3u);
+  EXPECT_EQ(GetWorstInteraction(), base::Milliseconds(3500));
+  EXPECT_EQ(GetHighPercentileInteraction(), base::Milliseconds(3500));
 
-  AddNewUserInteractions(3, *max_event_durations);
-  auto worst_ten_max_event_durations =
-      normalized_responsiveness_metrics()
-          .normalized_max_event_durations.worst_ten_latencies;
-  EXPECT_EQ(worst_ten_max_event_durations.size(), 3u);
-  EXPECT_EQ(worst_ten_max_event_durations.top(), base::Milliseconds(50));
-  worst_ten_max_event_durations.pop();
-  EXPECT_EQ(worst_ten_max_event_durations.top(), base::Milliseconds(100));
-  worst_ten_max_event_durations.pop();
-  EXPECT_EQ(worst_ten_max_event_durations.top(), base::Milliseconds(150));
+  // After adding 50 additional interactions, the high percentile should shift
+  // to the second highest interaction duration.
+  user_interaction_latencies.clear();
+  for (uint64_t i = 0; i < 50; i++) {
+    user_interaction_latencies.emplace_back(UserInteractionLatency::New(
+        base::Milliseconds(i + 100), UserInteractionType::kTapOrClick));
+  }
+  AddNewUserInteractions(50, *user_interaction_latencies_ptr);
+  EXPECT_EQ(GetNumInteractions(), 53u);
+  EXPECT_EQ(GetWorstInteraction(), base::Milliseconds(3500));
+  EXPECT_EQ(GetHighPercentileInteraction(), base::Milliseconds(3000));
 
-  auto& normalized_max_event_durations =
-      normalized_responsiveness_metrics().normalized_max_event_durations;
-  EXPECT_EQ(normalized_max_event_durations.worst_latency,
-            base::Milliseconds(150));
-}
\ No newline at end of file
+  // After adding 50 more interactions, the high percentile should shift
+  // to the third highest interaction duration.
+  user_interaction_latencies.clear();
+  for (uint64_t i = 0; i < 50; i++) {
+    user_interaction_latencies.emplace_back(UserInteractionLatency::New(
+        base::Milliseconds(300 - i), UserInteractionType::kTapOrClick));
+  }
+  AddNewUserInteractions(50, *user_interaction_latencies_ptr);
+  EXPECT_EQ(GetNumInteractions(), 103u);
+  EXPECT_EQ(GetWorstInteraction(), base::Milliseconds(3500));
+  EXPECT_EQ(GetHighPercentileInteraction(), base::Milliseconds(2000));
+}
+
+TEST_F(ResponsivenessMetricsNormalizationTest, TooManyInteractions) {
+  // Test what happens when there are so many interactions that the INP index
+  // goes out of the worst_ten_latencies_ bounds.
+  for (uint64_t i = 0; i < 500; i++) {
+    UserInteractionLatenciesPtr user_interaction_latencies_ptr =
+        UserInteractionLatencies::NewUserInteractionLatencies({});
+    auto& user_interaction_latencies =
+        user_interaction_latencies_ptr->get_user_interaction_latencies();
+    user_interaction_latencies.emplace_back(UserInteractionLatency::New(
+        base::Milliseconds(3000 - i), UserInteractionType::kTapOrClick));
+    user_interaction_latencies.emplace_back(UserInteractionLatency::New(
+        base::Milliseconds(2000 - (i)), UserInteractionType::kTapOrClick));
+    AddNewUserInteractions(2, *user_interaction_latencies_ptr);
+  }
+  EXPECT_EQ(GetNumInteractions(), 1000u);
+  EXPECT_EQ(GetWorstInteraction(), base::Milliseconds(3000));
+  EXPECT_EQ(GetHighPercentileInteraction(), base::Milliseconds(2991));
+}
diff --git a/components/permissions/vector_icons/BUILD.gn b/components/permissions/vector_icons/BUILD.gn
index f2043afb..c0d2cd0 100644
--- a/components/permissions/vector_icons/BUILD.gn
+++ b/components/permissions/vector_icons/BUILD.gn
@@ -7,10 +7,7 @@
 aggregate_vector_icons("permissions_vector_icons") {
   icon_directory = "."
 
-  sources = [
-    "accessibility.icon",
-    "usb_security_key.icon",
-  ]
+  sources = [ "accessibility.icon" ]
 }
 
 source_set("vector_icons") {
diff --git a/components/permissions/vector_icons/usb_security_key.icon b/components/permissions/vector_icons/usb_security_key.icon
deleted file mode 100644
index 207e803..0000000
--- a/components/permissions/vector_icons/usb_security_key.icon
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 4,
-MOVE_TO, 0.34f, 3.5f,
-R_H_LINE_TO, 3.03f,
-R_ARC_TO, 0.35f, 0.35f, 0, 0, 0, 0.34f, -0.35f,
-V_LINE_TO, 0.68f,
-R_ARC_TO, 0.35f, 0.35f, 0, 0, 0, -0.34f, -0.35f,
-H_LINE_TO, 0.34f,
-ARC_TO, 0.35f, 0.35f, 0, 0, 0, 0, 0.68f,
-R_V_LINE_TO, 2.47f,
-R_CUBIC_TO, 0, 0.19f, 0.15f, 0.35f, 0.34f, 0.35f,
-CLOSE,
-R_MOVE_TO, 1.52f, -0.93f,
-R_H_LINE_TO, 1.32f,
-R_V_LINE_TO, 0.4f,
-H_LINE_TO, 1.85f,
-CLOSE,
-R_MOVE_TO, 0, -0.79f,
-R_H_LINE_TO, 1.32f,
-R_V_LINE_TO, 0.4f,
-H_LINE_TO, 1.85f,
-CLOSE,
-R_MOVE_TO, 0, -0.93f,
-R_H_LINE_TO, 1.32f,
-R_V_LINE_TO, 0.4f,
-H_LINE_TO, 1.85f,
-CLOSE
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/FullRestoreMode.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/FullRestoreMode.yaml
index 5b5934d..268e69b1 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/FullRestoreMode.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/FullRestoreMode.yaml
@@ -20,7 +20,7 @@
   caption: Do not restore the last session.
   value: 3
 owners:
-- mbid@google.com
+- aninak@chromium.org
 - imprivata-eng@google.com
 schema:
   enum:
@@ -28,7 +28,7 @@
   - 2
   - 3
   type: integer
-future_on:
-- chrome_os
+supported_on:
+- chrome_os:121-
 tags: []
 type: int-enum
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.cc b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.cc
index ad3793fb..f106173 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.cc
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.cc
@@ -191,6 +191,30 @@
 PrivacySandboxSettingsImpl::Status PrivacySandboxAttestations::IsSiteAttested(
     const net::SchemefulSite& site,
     PrivacySandboxAttestationsGatedAPI invoking_api) const {
+  PrivacySandboxSettingsImpl::Status status =
+      IsSiteAttestedInternal(site, invoking_api);
+  base::UmaHistogramEnumeration(kAttestationStatusUMA, status);
+
+  // If the attestations map is absent and feature
+  // `kDefaultAllowPrivacySandboxAttestations` is on, default allow.
+  switch (status) {
+    case PrivacySandboxSettingsImpl::Status::kAttestationsFileNotYetReady:
+    case PrivacySandboxSettingsImpl::Status::
+        kAttestationsDownloadedNotYetLoaded:
+    case PrivacySandboxSettingsImpl::Status::kAttestationsFileCorrupt:
+      return base::FeatureList::IsEnabled(
+                 kDefaultAllowPrivacySandboxAttestations)
+                 ? PrivacySandboxSettingsImpl::Status::kAllowed
+                 : status;
+    default:
+      return status;
+  }
+}
+
+PrivacySandboxSettingsImpl::Status
+PrivacySandboxAttestations::IsSiteAttestedInternal(
+    const net::SchemefulSite& site,
+    PrivacySandboxAttestationsGatedAPI invoking_api) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // If attestations aren't enabled, pass the check trivially.
   if (!base::FeatureList::IsEnabled(
@@ -208,7 +232,7 @@
     return PrivacySandboxSettingsImpl::Status::kAllowed;
   }
 
-  // When the attestations map is not present, the behavior is default-deny.
+  // When the attestations map is not present, return the reason.
   if (!attestations_map_.has_value()) {
     // Break down by type of failure.
 
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h
index 4b90f94..5123aaf 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h
@@ -99,11 +99,12 @@
       delete;
   PrivacySandboxAttestations& operator=(PrivacySandboxAttestations&&);
 
-  // Returns whether `site` is enrolled and attested for `invoking_api`.
-  // This function returns true unconditionally if
-  // 1. The `kEnforcePrivacySandboxAttestations` flag is disabled.
-  // 2. Or `is_all_apis_attested_for_testing_` is set to true by
-  // `SetAllPrivacySandboxAttestedForTesting()` for testing.
+  // Record the status returned by `IsSiteAttestedInternal` to a histogram, then
+  // return the status.
+  // TODO(crbug.com/1500636): This method will occasionally return false
+  // positives i.e. it may mark some sites as attested even when they are not.
+  // This will occur for example, if the attestations file is corrupted on-disk,
+  // or the file is otherwise unavailable.
   PrivacySandboxSettingsImpl::Status IsSiteAttested(
       const net::SchemefulSite& site,
       PrivacySandboxAttestationsGatedAPI invoking_api) const;
@@ -164,6 +165,15 @@
   // class.
   PrivacySandboxAttestations();
 
+  // Returns whether `site` is enrolled and attested for `invoking_api`.
+  // This function returns `kAllowed` unconditionally if
+  // 1. The `kEnforcePrivacySandboxAttestations` flag is disabled.
+  // 2. Or `is_all_apis_attested_for_testing_` is set to true by
+  // `SetAllPrivacySandboxAttestedForTesting()` for testing.
+  PrivacySandboxSettingsImpl::Status IsSiteAttestedInternal(
+      const net::SchemefulSite& site,
+      PrivacySandboxAttestationsGatedAPI invoking_api) const;
+
   // Invoke the `attestations_loaded_callback_` registered by tests, if any.
   void RunLoadAttestationsDoneCallbackForTesting();
 
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_histograms.h b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_histograms.h
index e124e70..85410be 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_histograms.h
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_histograms.h
@@ -7,6 +7,8 @@
 
 namespace privacy_sandbox {
 
+inline constexpr char kAttestationStatusUMA[] =
+    "PrivacySandbox.Attestations.IsSiteAttested";
 inline constexpr char kAttestationsFileParsingUMA[] =
     "PrivacySandbox.Attestations.InitializationDuration.Parsing";
 inline constexpr char kAttestationsMapMemoryUsageUMA[] =
diff --git a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_unittest.cc b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_unittest.cc
index 4f17e38..0dc6e7a 100644
--- a/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_unittest.cc
+++ b/components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/scoped_observation.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/with_feature_override.h"
 #include "base/version.h"
 #include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_histograms.h"
 #include "components/privacy_sandbox/privacy_sandbox_attestations/proto/privacy_sandbox_attestations.pb.h"
@@ -45,9 +46,14 @@
  protected:
   using Status = PrivacySandboxSettingsImpl::Status;
 
+  const base::HistogramTester& histogram_tester() const {
+    return histogram_tester_;
+  }
+
  private:
   content::BrowserTaskEnvironment browser_task_environment_;
   ScopedPrivacySandboxAttestations scoped_attestations_;
+  base::HistogramTester histogram_tester_;
 };
 
 TEST_F(PrivacySandboxAttestationsTestBase, AddOverride) {
@@ -69,12 +75,18 @@
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           site, PrivacySandboxAttestationsGatedAPI::kTopics);
   EXPECT_EQ(attestation_status, Status::kAttestationsFileNotYetReady);
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotYetReady, 1);
 }
 
 class PrivacySandboxAttestationsFeatureEnabledTest
-    : public PrivacySandboxAttestationsTestBase {
+    : public base::test::WithFeatureOverride,
+      public PrivacySandboxAttestationsTestBase {
  public:
-  PrivacySandboxAttestationsFeatureEnabledTest() {
+  PrivacySandboxAttestationsFeatureEnabledTest()
+      : base::test::WithFeatureOverride(
+            kDefaultAllowPrivacySandboxAttestations) {
     scoped_feature_list_.InitAndEnableFeature(
         privacy_sandbox::kEnforcePrivacySandboxAttestations);
   }
@@ -128,39 +140,69 @@
     run_loop.Run();
   }
 
+  bool IsAttestationsDefaultAllowed() { return IsParamFeatureEnabled(); }
+
+  // Return the final expected status of `IsSiteAttested` given the `status`
+  // which represents the actual status of the attestation.
+  Status GetExpectedStatus(Status status) {
+    // If the attestations map is absent and feature
+    // `kDefaultAllowPrivacySandboxAttestations` is on, the expected status is
+    // default allow when the given status implies the map is absent.
+    if (IsAttestationsDefaultAllowed() &&
+        (status == Status::kAttestationsFileNotYetReady ||
+         status == Status::kAttestationsDownloadedNotYetLoaded ||
+         status == Status::kAttestationsFileCorrupt)) {
+      return Status::kAllowed;
+    }
+
+    return status;
+  }
+
  private:
   base::ScopedTempDir scoped_temp_dir_;
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest,
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest,
        DefaultDenyIfAttestationsMapNotPresent) {
   net::SchemefulSite site(GURL("https://example.com"));
 
   Status attestation_status =
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           site, PrivacySandboxAttestationsGatedAPI::kTopics);
-  EXPECT_EQ(attestation_status, Status::kAttestationsFileNotYetReady);
+  EXPECT_EQ(attestation_status,
+            GetExpectedStatus(Status::kAttestationsFileNotYetReady));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotYetReady, 1);
 }
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest, AttestedIfOverridden) {
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest, AttestedIfOverridden) {
   net::SchemefulSite site(GURL("https://example.com"));
   Status attestation_status =
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           site, PrivacySandboxAttestationsGatedAPI::kTopics);
-  ASSERT_NE(attestation_status, Status::kAllowed);
+  ASSERT_EQ(attestation_status,
+            GetExpectedStatus(Status::kAttestationsFileNotYetReady));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotYetReady, 1);
 
   PrivacySandboxAttestations::GetInstance()->AddOverride(site);
   EXPECT_TRUE(PrivacySandboxAttestations::GetInstance()->IsOverridden(site));
 }
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest,
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest,
        EnrolledWithoutAttestations) {
   net::SchemefulSite site(GURL("https://example.com"));
   Status attestation_status =
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           site, PrivacySandboxAttestationsGatedAPI::kTopics);
-  ASSERT_NE(attestation_status, Status::kAllowed);
+  ASSERT_EQ(attestation_status,
+            GetExpectedStatus(Status::kAttestationsFileNotYetReady));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotYetReady, 1);
 
   PrivacySandboxAttestations::GetInstance()->SetAttestationsForTesting(
       PrivacySandboxAttestationsMap{{site, {}}});
@@ -168,15 +210,23 @@
   Status new_attestation_status =
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           site, PrivacySandboxAttestationsGatedAPI::kTopics);
-  EXPECT_NE(new_attestation_status, Status::kAllowed);
+  EXPECT_EQ(new_attestation_status,
+            GetExpectedStatus(Status::kAttestationFailed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 2);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationFailed, 1);
 }
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest, EnrolledAndAttested) {
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest, EnrolledAndAttested) {
   net::SchemefulSite site(GURL("https://example.com"));
   Status attestation_status =
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           site, PrivacySandboxAttestationsGatedAPI::kTopics);
-  ASSERT_NE(attestation_status, Status::kAllowed);
+  ASSERT_EQ(attestation_status,
+            GetExpectedStatus(Status::kAttestationsFileNotYetReady));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotYetReady, 1);
 
   PrivacySandboxAttestations::GetInstance()->SetAttestationsForTesting(
       PrivacySandboxAttestationsMap{
@@ -186,10 +236,13 @@
   Status new_attestation_status =
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           site, PrivacySandboxAttestationsGatedAPI::kTopics);
-  EXPECT_EQ(new_attestation_status, Status::kAllowed);
+  EXPECT_EQ(new_attestation_status, GetExpectedStatus(Status::kAllowed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 2);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA, Status::kAllowed,
+                                       1);
 }
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest,
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest,
        NonExistentAttestationsFile) {
   base::RunLoop run_loop;
   privacy_sandbox::PrivacySandboxAttestations::GetInstance()
@@ -209,7 +262,7 @@
 // The parsing progress may end up being
 // `PrivacySandboxAttestations::Progress::kFinished` but there is no in-memory
 // attestations map. Verify that the second attempt to parse should not crash.
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest,
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest,
        TryParseNonExistentAttestationsFileTwice) {
   base::RunLoop first_attempt;
   privacy_sandbox::PrivacySandboxAttestations::GetInstance()
@@ -238,7 +291,7 @@
                    .IsValid());
 }
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest,
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest,
        InvalidAttestationsFileIsNotLoaded) {
   // Write an invalid proto file, and try to parse it.
   WriteAttestationsFileAndWaitForLoading(base::Version("0.0.1"),
@@ -255,14 +308,14 @@
   Status attestation_status =
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           site, PrivacySandboxAttestationsGatedAPI::kTopics);
-  EXPECT_EQ(attestation_status, Status::kAttestationsFileCorrupt);
+  EXPECT_EQ(attestation_status,
+            GetExpectedStatus(Status::kAttestationsFileCorrupt));
 }
 
 // When parsing fails or crashes, a sentinel file is left in the installation
 // direction. This file prevents further parsing attempts.
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest,
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest,
        SentinelPreventsSubsequentParsingAfterCrashOrFailure) {
-  base::HistogramTester histogram_tester;
   // Write an invalid proto file, and try to parse it. Note here we are not
   // using `WriteAttestationsFileAndWaitForLoading()` because we need the second
   // attempt to parse to be in the same installation directory.
@@ -293,7 +346,7 @@
   EXPECT_FALSE(PrivacySandboxAttestations::GetInstance()
                    ->GetVersionForTesting()
                    .IsValid());
-  histogram_tester.ExpectTotalCount(kAttestationsFileParsingUMA, 0);
+  histogram_tester().ExpectTotalCount(kAttestationsFileParsingUMA, 0);
 
   // Attempts to check attestation status should return that the file is
   // corrupt.
@@ -302,7 +355,11 @@
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           net::SchemefulSite(GURL(site)),
           PrivacySandboxAttestationsGatedAPI::kTopics);
-  EXPECT_EQ(attestation_status, Status::kAttestationsFileCorrupt);
+  EXPECT_EQ(attestation_status,
+            GetExpectedStatus(Status::kAttestationsFileCorrupt));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileCorrupt, 1);
   ASSERT_TRUE(
       base::PathExists(install_dir.GetPath().Append(kSentinelFileName)));
 
@@ -349,8 +406,12 @@
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           net::SchemefulSite(GURL(site)),
           PrivacySandboxAttestationsGatedAPI::kTopics);
-  EXPECT_EQ(attestation_status, Status::kAttestationsFileCorrupt);
-  histogram_tester.ExpectTotalCount(kAttestationsFileParsingUMA, 0);
+  EXPECT_EQ(attestation_status,
+            GetExpectedStatus(Status::kAttestationsFileCorrupt));
+  histogram_tester().ExpectTotalCount(kAttestationsFileParsingUMA, 0);
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 2);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileCorrupt, 2);
 
   // Create a new version valid attestations file which is in a different
   // directory.
@@ -372,7 +433,7 @@
       base::Version("0.0.2"), new_version_file_path);
   parsing_new_version.Run();
 
-  histogram_tester.ExpectTotalCount(kAttestationsFileParsingUMA, 1);
+  histogram_tester().ExpectTotalCount(kAttestationsFileParsingUMA, 1);
 
   // The new version should be loaded successfully.
   EXPECT_EQ(PrivacySandboxAttestations::GetInstance()->GetVersionForTesting(),
@@ -381,12 +442,13 @@
       PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
           net::SchemefulSite(GURL(site)),
           PrivacySandboxAttestationsGatedAPI::kTopics);
-  EXPECT_EQ(attestation_status, Status::kAllowed);
+  EXPECT_EQ(attestation_status, GetExpectedStatus(Status::kAllowed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 3);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA, Status::kAllowed,
+                                       1);
 }
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest, LoadAttestationsFile) {
-  base::HistogramTester histogram_tester;
-
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest, LoadAttestationsFile) {
   MockAttestationsObserver observer;
   base::ScopedObservation<PrivacySandboxAttestations,
                           content::PrivacySandboxAttestationsObserver>
@@ -402,7 +464,10 @@
   ASSERT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAttestationsFileNotYetReady);
+            GetExpectedStatus(Status::kAttestationsFileNotYetReady));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotYetReady, 1);
 
   // Add attestation for the site.
   PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto
@@ -415,8 +480,8 @@
 
   WriteAttestationsFileAndWaitForLoading(base::Version("0.0.1"),
                                          serialized_proto);
-  histogram_tester.ExpectTotalCount(kAttestationsFileParsingUMA, 1);
-  histogram_tester.ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 1);
+  histogram_tester().ExpectTotalCount(kAttestationsFileParsingUMA, 1);
+  histogram_tester().ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 1);
 
   // The site should be attested for the API.
   ASSERT_TRUE(PrivacySandboxAttestations::GetInstance()
@@ -427,13 +492,19 @@
   EXPECT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAllowed);
+            GetExpectedStatus(Status::kAllowed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 2);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA, Status::kAllowed,
+                                       1);
   // For API not in the attestations list, the result should be
   // `kAttestationFailed`.
   EXPECT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kProtectedAudience),
-            Status::kAttestationFailed);
+            GetExpectedStatus(Status::kAttestationFailed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 3);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationFailed, 1);
 
   // Add attestation for Protected Audience.
   site_attestation.add_attested_apis(PROTECTED_AUDIENCE);
@@ -443,8 +514,8 @@
   proto.SerializeToString(&serialized_proto);
   WriteAttestationsFileAndWaitForLoading(base::Version("0.0.2"),
                                          serialized_proto);
-  histogram_tester.ExpectTotalCount(kAttestationsFileParsingUMA, 2);
-  histogram_tester.ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 2);
+  histogram_tester().ExpectTotalCount(kAttestationsFileParsingUMA, 2);
+  histogram_tester().ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 2);
 
   // Now the site should be attested for both APIs.
   ASSERT_TRUE(PrivacySandboxAttestations::GetInstance()
@@ -455,14 +526,20 @@
   EXPECT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAllowed);
+            GetExpectedStatus(Status::kAllowed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 4);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA, Status::kAllowed,
+                                       2);
   EXPECT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kProtectedAudience),
-            Status::kAllowed);
+            GetExpectedStatus(Status::kAllowed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 5);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA, Status::kAllowed,
+                                       3);
 }
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest,
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest,
        LoadAttestationsFilePauseDuringParsing) {
   PrivacySandboxAttestationsProto proto;
   ASSERT_TRUE(proto.site_attestations_size() == 0);
@@ -471,7 +548,10 @@
   ASSERT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAttestationsFileNotYetReady);
+            GetExpectedStatus(Status::kAttestationsFileNotYetReady));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotYetReady, 1);
 
   // Add attestation for the site.
   PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto
@@ -490,12 +570,14 @@
   EXPECT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAttestationsDownloadedNotYetLoaded);
+            GetExpectedStatus(Status::kAttestationsDownloadedNotYetLoaded));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 2);
+  histogram_tester().ExpectBucketCount(
+      kAttestationStatusUMA, Status::kAttestationsDownloadedNotYetLoaded, 1);
 }
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest,
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest,
        OlderVersionAttestationsFileIsNotLoaded) {
-  base::HistogramTester histogram_tester;
   PrivacySandboxAttestationsProto proto;
   ASSERT_TRUE(proto.site_attestations_size() == 0);
 
@@ -503,7 +585,10 @@
   ASSERT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAttestationsFileNotYetReady);
+            GetExpectedStatus(Status::kAttestationsFileNotYetReady));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotYetReady, 1);
 
   // Add attestation for the site.
   PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto
@@ -516,8 +601,8 @@
 
   WriteAttestationsFileAndWaitForLoading(base::Version("1.2.3"),
                                          serialized_proto);
-  histogram_tester.ExpectTotalCount(kAttestationsFileParsingUMA, 1);
-  histogram_tester.ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 1);
+  histogram_tester().ExpectTotalCount(kAttestationsFileParsingUMA, 1);
+  histogram_tester().ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 1);
 
   // The site should be attested for the API.
   ASSERT_TRUE(PrivacySandboxAttestations::GetInstance()
@@ -528,7 +613,10 @@
   ASSERT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAllowed);
+            GetExpectedStatus(Status::kAllowed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 2);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA, Status::kAllowed,
+                                       1);
 
   // Clear the proto attestations.
   proto.clear_site_attestations();
@@ -537,8 +625,8 @@
   proto.SerializeToString(&serialized_proto);
   WriteAttestationsFileAndWaitForLoading(base::Version("0.0.1"),
                                          serialized_proto);
-  histogram_tester.ExpectTotalCount(kAttestationsFileParsingUMA, 1);
-  histogram_tester.ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 1);
+  histogram_tester().ExpectTotalCount(kAttestationsFileParsingUMA, 1);
+  histogram_tester().ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 1);
 
   // The attestations map should still be the old one.
   ASSERT_TRUE(PrivacySandboxAttestations::GetInstance()
@@ -549,12 +637,14 @@
   EXPECT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAllowed);
+            GetExpectedStatus(Status::kAllowed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 3);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA, Status::kAllowed,
+                                       2);
 }
 
-TEST_F(PrivacySandboxAttestationsFeatureEnabledTest,
+TEST_P(PrivacySandboxAttestationsFeatureEnabledTest,
        NewerVersionAttestationsFileIsLoaded) {
-  base::HistogramTester histogram_tester;
   PrivacySandboxAttestationsProto proto;
   ASSERT_TRUE(proto.site_attestations_size() == 0);
 
@@ -562,7 +652,10 @@
   ASSERT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAttestationsFileNotYetReady);
+            GetExpectedStatus(Status::kAttestationsFileNotYetReady));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 1);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationsFileNotYetReady, 1);
 
   // Add attestation for the site.
   PrivacySandboxAttestationsProto::PrivacySandboxAttestedAPIsProto
@@ -574,8 +667,8 @@
   proto.SerializeToString(&serialized_proto);
   WriteAttestationsFileAndWaitForLoading(base::Version("0.0.1"),
                                          serialized_proto);
-  histogram_tester.ExpectTotalCount(kAttestationsFileParsingUMA, 1);
-  histogram_tester.ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 1);
+  histogram_tester().ExpectTotalCount(kAttestationsFileParsingUMA, 1);
+  histogram_tester().ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 1);
 
   // The site should be attested for the API.
   ASSERT_TRUE(PrivacySandboxAttestations::GetInstance()
@@ -586,7 +679,10 @@
   ASSERT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAllowed);
+            GetExpectedStatus(Status::kAllowed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 2);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA, Status::kAllowed,
+                                       1);
 
   // Clear the attestations.
   proto.clear_site_attestations();
@@ -595,8 +691,8 @@
   proto.SerializeToString(&serialized_proto);
   WriteAttestationsFileAndWaitForLoading(base::Version("0.0.2"),
                                          serialized_proto);
-  histogram_tester.ExpectTotalCount(kAttestationsFileParsingUMA, 2);
-  histogram_tester.ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 2);
+  histogram_tester().ExpectTotalCount(kAttestationsFileParsingUMA, 2);
+  histogram_tester().ExpectTotalCount(kAttestationsMapMemoryUsageUMA, 2);
 
   // The newer version should override the existing attestations map.
   ASSERT_TRUE(PrivacySandboxAttestations::GetInstance()
@@ -610,7 +706,13 @@
   EXPECT_EQ(PrivacySandboxAttestations::GetInstance()->IsSiteAttested(
                 net::SchemefulSite(GURL(site)),
                 PrivacySandboxAttestationsGatedAPI::kTopics),
-            Status::kAttestationFailed);
+            GetExpectedStatus(Status::kAttestationFailed));
+  histogram_tester().ExpectTotalCount(kAttestationStatusUMA, 3);
+  histogram_tester().ExpectBucketCount(kAttestationStatusUMA,
+                                       Status::kAttestationFailed, 1);
 }
 
+INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(
+    PrivacySandboxAttestationsFeatureEnabledTest);
+
 }  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_features.cc b/components/privacy_sandbox/privacy_sandbox_features.cc
index b24f547..d9faab6e 100644
--- a/components/privacy_sandbox/privacy_sandbox_features.cc
+++ b/components/privacy_sandbox/privacy_sandbox_features.cc
@@ -110,6 +110,10 @@
              "EnforcePrivacySandboxAttestations",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kDefaultAllowPrivacySandboxAttestations,
+             "DefaultAllowPrivacySandboxAttestations",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 const char kPrivacySandboxEnrollmentOverrides[] =
     "privacy-sandbox-enrollment-overrides";
 
diff --git a/components/privacy_sandbox/privacy_sandbox_features.h b/components/privacy_sandbox/privacy_sandbox_features.h
index 37f9406..ca9c604 100644
--- a/components/privacy_sandbox/privacy_sandbox_features.h
+++ b/components/privacy_sandbox/privacy_sandbox_features.h
@@ -150,6 +150,11 @@
 COMPONENT_EXPORT(PRIVACY_SANDBOX_FEATURES)
 BASE_DECLARE_FEATURE(kEnforcePrivacySandboxAttestations);
 
+// Enable the Privacy Sandbox Attestations to default allow when the
+// attestations map is absent.
+COMPONENT_EXPORT(PRIVACY_SANDBOX_FEATURES)
+BASE_DECLARE_FEATURE(kDefaultAllowPrivacySandboxAttestations);
+
 // Gives a list of sites permission to use Privacy Sandbox features without
 // being officially enrolled.
 COMPONENT_EXPORT(PRIVACY_SANDBOX_FEATURES)
diff --git a/components/privacy_sandbox/privacy_sandbox_settings_impl_unittest.cc b/components/privacy_sandbox/privacy_sandbox_settings_impl_unittest.cc
index 154a4f8..f77365c 100644
--- a/components/privacy_sandbox/privacy_sandbox_settings_impl_unittest.cc
+++ b/components/privacy_sandbox/privacy_sandbox_settings_impl_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_command_line.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/with_feature_override.h"
 #include "components/browsing_topics/test_util.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
@@ -1531,9 +1532,12 @@
            static_cast<int>(Status::kAllowed)}});
 }
 
-class PrivacySandboxAttestationsTest : public PrivacySandboxSettingsM1Test {
+class PrivacySandboxAttestationsTest : public base::test::WithFeatureOverride,
+                                       public PrivacySandboxSettingsM1Test {
  public:
-  PrivacySandboxAttestationsTest() {
+  PrivacySandboxAttestationsTest()
+      : base::test::WithFeatureOverride(
+            kDefaultAllowPrivacySandboxAttestations) {
     // This test suite tests Privacy Sandbox Attestations related behaviors,
     // turn off the setting that makes all APIs considered attested.
     privacy_sandbox::PrivacySandboxAttestations::GetInstance()
@@ -1544,11 +1548,15 @@
     feature_list_.InitAndEnableFeature(
         privacy_sandbox::kPrivacySandboxSettings4);
   }
+
+  bool IsAttestationsDefaultAllowed() { return IsParamFeatureEnabled(); }
 };
 
 // When the attestations map has not yet been downloaded, or the browser hasn't
-// confirmed that it is present in the filesystem, attestation fails.
-TEST_F(PrivacySandboxAttestationsTest, AttestationsFileNotYetReady) {
+// confirmed that it is present in the filesystem, attestation:
+// 1. succeeds if `kDefaultAllowPrivacySandboxAttestations` is on.
+// 2. fails otherwise.
+TEST_P(PrivacySandboxAttestationsTest, AttestationsFileNotYetReady) {
   GURL top_frame_url("https://top-frame.com");
   GURL enrollee_url("https://embedded.com");
   RunTestCase(
@@ -1580,7 +1588,7 @@
                kIsEventReportingDestinationAttestedForSharedStorage,
                kIsSharedStorageAllowed, kIsPrivateAggregationAllowed,
                kIsPrivateAggregationDebugModeAllowed},
-           false},
+           IsAttestationsDefaultAllowed()},
           {MultipleOutputKeys{
                kIsTopicsAllowedForContextMetric,
                kIsAttributionReportingAllowedMetric,
@@ -1591,12 +1599,14 @@
                kIsPrivateAggregationAllowedMetric,
                kIsEventReportingDestinationAttestedForSharedStorageMetric,
                kIsEventReportingDestinationAttestedForFledgeMetric},
-           static_cast<int>(Status::kAttestationsFileNotYetReady)}});
+           static_cast<int>(IsAttestationsDefaultAllowed()
+                                ? Status::kAllowed
+                                : Status::kAttestationsFileNotYetReady)}});
 }
 
 // When the attestations map has no enrollments at all (i.e., no enrollment
 // for the site in question), attestation fails.
-TEST_F(PrivacySandboxAttestationsTest, NoEnrollments) {
+TEST_P(PrivacySandboxAttestationsTest, NoEnrollments) {
   GURL top_frame_url("https://top-frame.com");
   GURL enrollee_url("https://embedded.com");
   RunTestCase(
@@ -1642,7 +1652,7 @@
 
 // When the site in question is enrolled but has no attestations at all (i.e.,
 // no attestation for the API in question), attestation fails.
-TEST_F(PrivacySandboxAttestationsTest, EnrollmentWithoutAttestations) {
+TEST_P(PrivacySandboxAttestationsTest, EnrollmentWithoutAttestations) {
   GURL top_frame_url("https://top-frame.com");
   GURL enrollee_url("https://embedded.com");
   RunTestCase(
@@ -1688,7 +1698,7 @@
            static_cast<int>(Status::kAttestationFailed)}});
 }
 
-TEST_F(PrivacySandboxAttestationsTest, TopicsAttestation) {
+TEST_P(PrivacySandboxAttestationsTest, TopicsAttestation) {
   GURL top_frame_url("https://top-frame.com");
   GURL enrollee_url("https://embedded.com");
   RunTestCase(
@@ -1737,7 +1747,7 @@
            static_cast<int>(Status::kAttestationFailed)}});
 }
 
-TEST_F(PrivacySandboxAttestationsTest, PrivateAggregationAttestation) {
+TEST_P(PrivacySandboxAttestationsTest, PrivateAggregationAttestation) {
   GURL top_frame_url("https://top-frame.com");
   GURL enrollee_url("https://embedded.com");
   RunTestCase(
@@ -1788,7 +1798,7 @@
            static_cast<int>(Status::kAttestationFailed)}});
 }
 
-TEST_F(PrivacySandboxAttestationsTest, SharedStorageAttestation) {
+TEST_P(PrivacySandboxAttestationsTest, SharedStorageAttestation) {
   GURL top_frame_url("https://top-frame.com");
   GURL enrollee_url("https://embedded.com");
   RunTestCase(
@@ -1840,7 +1850,7 @@
            static_cast<int>(Status::kAttestationFailed)}});
 }
 
-TEST_F(PrivacySandboxAttestationsTest, FledgeAttestation) {
+TEST_P(PrivacySandboxAttestationsTest, FledgeAttestation) {
   GURL top_frame_url("https://top-frame.com");
   GURL enrollee_url("https://embedded.com");
   RunTestCase(
@@ -1892,7 +1902,7 @@
            static_cast<int>(Status::kAttestationFailed)}});
 }
 
-TEST_F(PrivacySandboxAttestationsTest, FledgeAttestationBlockJoiningEtldplus1) {
+TEST_P(PrivacySandboxAttestationsTest, FledgeAttestationBlockJoiningEtldplus1) {
   GURL top_frame_url("https://top-frame.com");
   GURL enrollee_url("https://embedded.com");
   RunTestCase(
@@ -1945,7 +1955,7 @@
            static_cast<int>(Status::kAttestationFailed)}});
 }
 
-TEST_F(PrivacySandboxAttestationsTest, AttributionReportingAttestation) {
+TEST_P(PrivacySandboxAttestationsTest, AttributionReportingAttestation) {
   GURL top_frame_url("https://top-frame.com");
   GURL enrollee_url("https://embedded.com");
   RunTestCase(
@@ -1997,7 +2007,7 @@
            static_cast<int>(Status::kAttestationFailed)}});
 }
 
-TEST_F(PrivacySandboxAttestationsTest, SetOverrideFromDevtools) {
+TEST_P(PrivacySandboxAttestationsTest, SetOverrideFromDevtools) {
   privacy_sandbox_test_util::SetupTestState(
       prefs(), host_content_settings_map(),
       /*privacy_sandbox_enabled=*/true,
@@ -2008,6 +2018,11 @@
       /*managed_cookie_exceptions=*/{});
   privacy_sandbox_settings()->SetAllPrivacySandboxAllowedForTesting();
 
+  // Set an empty attestations map to prevent the API being default allowed
+  // when feature `kDefaultAllowPrivacySandboxAttestations` is on.
+  privacy_sandbox::PrivacySandboxAttestations::GetInstance()
+      ->SetAttestationsForTesting(PrivacySandboxAttestationsMap{});
+
   GURL top_level_url("https://top-level-origin.com");
   GURL caller_url("https://embedded.com");
 
@@ -2028,7 +2043,7 @@
       privacy_sandbox::PrivacySandboxAttestationsGatedAPI::kProtectedAudience));
 }
 
-TEST_F(PrivacySandboxAttestationsTest, SetOverrideFromFlags) {
+TEST_P(PrivacySandboxAttestationsTest, SetOverrideFromFlags) {
   static const struct TestCase {
     std::string name;
     std::string flags;
@@ -2056,6 +2071,11 @@
   privacy_sandbox_settings()->SetAllPrivacySandboxAllowedForTesting();
   base::test::ScopedCommandLine scoped_command_line;
 
+  // Set an empty attestations map to prevent the API being default allowed
+  // when feature `kDefaultAllowPrivacySandboxAttestations` is on.
+  privacy_sandbox::PrivacySandboxAttestations::GetInstance()
+      ->SetAttestationsForTesting(PrivacySandboxAttestationsMap{});
+
   for (const auto& test : kTestCases) {
     // Reset the overrides flags from the previous test loop.
     scoped_command_line.GetProcessCommandLine()->RemoveSwitch(
@@ -2081,6 +2101,8 @@
   }
 }
 
+INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(PrivacySandboxAttestationsTest);
+
 struct PrivacySandbox3pcdTestCase {
   std::string disable_ads_apis_param = "false";
   bool m1_topics_enabled_pref_consent = true;
diff --git a/components/search_engines/search_engine_choice_utils.h b/components/search_engines/search_engine_choice_utils.h
index af7621f..ce9dbe8b 100644
--- a/components/search_engines/search_engine_choice_utils.h
+++ b/components/search_engines/search_engine_choice_utils.h
@@ -68,7 +68,14 @@
   kLearnMoreWasDisplayed = 5,
   // The "Learn more" screen was displayed on the FRE-specific screen.
   kFreLearnMoreWasDisplayed = 6,
-  kMaxValue = kFreLearnMoreWasDisplayed,
+  // The profile creation specific flavor of the screen was displayed.
+  kProfileCreationChoiceScreenWasDisplayed = 7,
+  // The user clicked `Set as default` on the profile creation specific screen.
+  kProfileCreationDefaultWasSet = 8,
+  // The "Learn more" screen was displayed on the profile creation specific
+  // screen.
+  kProfileCreationLearnMoreDisplayed = 9,
+  kMaxValue = kProfileCreationLearnMoreDisplayed,
 };
 
 // Profile properties that need to be passed to
diff --git a/components/segmentation_platform/internal/data_collection/training_data_collector_impl.cc b/components/segmentation_platform/internal/data_collection/training_data_collector_impl.cc
index e4a23eb..b5a68d7 100644
--- a/components/segmentation_platform/internal/data_collection/training_data_collector_impl.cc
+++ b/components/segmentation_platform/internal/data_collection/training_data_collector_impl.cc
@@ -198,10 +198,10 @@
                   << " with ModelSource is "
                   << proto::ModelSource_Name(segment_info.model_source());
           // Observation is reached for the current training data.
-          OnObservationTrigger(
-              absl::nullopt,
+          PostObservationTask(
               TrainingRequestId::FromUnsafeValue(training_data.request_id()),
-              segment_info, base::DoNothing());
+              segment_info, base::TimeDelta(),
+              stats::TrainingDataCollectionEvent::kDelayedTaskPosted);
         }
       }
     }
diff --git a/components/segmentation_platform/internal/execution/model_executor_impl.cc b/components/segmentation_platform/internal/execution/model_executor_impl.cc
index accc1e5..8a01225 100644
--- a/components/segmentation_platform/internal/execution/model_executor_impl.cc
+++ b/components/segmentation_platform/internal/execution/model_executor_impl.cc
@@ -61,7 +61,8 @@
   std::unique_ptr<ModelExecutionTraceEvent> trace_event;
 
   SegmentId segment_id = SegmentId::OPTIMIZATION_TARGET_UNKNOWN;
-  proto::SegmentInfo segment_info;
+  proto::ModelSource model_source = proto::ModelSource::DEFAULT_MODEL_SOURCE;
+  int64_t model_version = 0;
   raw_ptr<ModelProvider, AcrossTasksDanglingUntriaged> model_provider = nullptr;
   bool record_metrics_for_default = false;
   ModelExecutionCallback callback;
@@ -106,10 +107,8 @@
   // fully processed.
   auto state = std::make_unique<ExecutionState>();
   state->segment_id = request->segment_id;
-  // TODO(ssid): Make segment_info optional in ExecutionState.
-  if (segment_info) {
-    state->segment_info = *segment_info;
-  }
+  state->model_source = request->model_source;
+  state->model_version = segment_info->model_version();
 
   state->model_provider = request->model_provider;
   state->record_metrics_for_default =
@@ -176,11 +175,10 @@
     for (unsigned i = 0; i < state->input_tensor.size(); ++i)
       log_input << " feature " << i << ": " << state->input_tensor[i];
     VLOG(1) << "Segmentation model input: " << log_input.str()
-            << " for segment "
-            << proto::SegmentId_Name(state->segment_info.segment_id());
+            << " for segment " << proto::SegmentId_Name(state->segment_id);
   }
   const ModelProvider::Request& const_input_tensor = state->input_tensor;
-  stats::RecordModelExecutionZeroValuePercent(state->segment_info.segment_id(),
+  stats::RecordModelExecutionZeroValuePercent(state->segment_id,
                                               const_input_tensor);
   state->model_execution_start_time = clock_->Now();
   ModelProvider* model = state->model_provider;
@@ -196,7 +194,7 @@
   ModelExecutionTraceEvent trace_event(
       "ModelExecutorImpl::OnModelExecutionComplete", *state);
   stats::RecordModelExecutionDurationModel(
-      state->segment_info.segment_id(), result.has_value(),
+      state->segment_id, result.has_value(),
       clock_->Now() - state->model_execution_start_time);
   if (result.has_value() && result.value().size() > 0) {
     if (VLOG_IS_ON(1)) {
@@ -205,33 +203,37 @@
         log_output << " output " << i << ": " << result.value().at(i);
       }
       VLOG(1) << "Segmentation model result: " << log_output.str()
-              << " for segment "
-              << proto::SegmentId_Name(state->segment_info.segment_id());
+              << " for segment " << proto::SegmentId_Name(state->segment_id);
     }
+
+    const SegmentInfo* latest_info = segment_db_->GetCachedSegmentInfo(
+        state->segment_id, state->model_source);
+    if (latest_info->model_version() != state->model_version) {
+      // The version could have changed if new model is downloaded during
+      // execution. This should not affect the metrics recording below.
+      LOG(ERROR) << "Model version changed during execution";
+    }
+
     const proto::SegmentationModelMetadata& model_metadata =
-        state->segment_info.model_metadata();
+        latest_info->model_metadata();
     if (model_metadata.has_output_config()) {
-      stats::RecordModelExecutionResult(state->segment_info.segment_id(),
-                                        result.value(),
+      stats::RecordModelExecutionResult(state->segment_id, result.value(),
                                         model_metadata.output_config());
     } else {
-      stats::RecordModelExecutionResult(state->segment_info.segment_id(),
-                                        result.value().at(0),
+      stats::RecordModelExecutionResult(state->segment_id, result.value().at(0),
                                         model_metadata.return_type());
     }
 
     base::TimeDelta signal_storage_length =
         model_metadata.signal_storage_length() *
         metadata_utils::GetTimeUnit(model_metadata);
-    if (state->segment_info.model_version() &&
-        state->segment_info.model_source() ==
-            proto::ModelSource::SERVER_MODEL_SOURCE &&
+    if (state->model_version &&
+        state->model_source == proto::ModelSource::SERVER_MODEL_SOURCE &&
         SegmentationUkmHelper::AllowedToUploadData(signal_storage_length,
                                                    clock_)) {
       if (state->upload_tensors) {
         SegmentationUkmHelper::GetInstance()->RecordModelExecutionResult(
-            state->segment_info.segment_id(),
-            state->segment_info.model_version(), state->input_tensor,
+            state->segment_id, state->model_version, state->input_tensor,
             result.value());
       }
     }
@@ -241,7 +243,7 @@
                                   std::move(input_tensor), *result));
   } else {
     VLOG(1) << "Segmentation model returned no result for segment "
-            << proto::SegmentId_Name(state->segment_info.segment_id());
+            << proto::SegmentId_Name(state->segment_id);
     RunModelExecutionCallback(*state, std::move(state->callback),
                               std::make_unique<ModelExecutionResult>(
                                   ModelExecutionStatus::kExecutionError));
diff --git a/components/sync/android/java/src/org/chromium/components/sync/SyncServiceImpl.java b/components/sync/android/java/src/org/chromium/components/sync/SyncServiceImpl.java
index db3d509..a45ded7 100644
--- a/components/sync/android/java/src/org/chromium/components/sync/SyncServiceImpl.java
+++ b/components/sync/android/java/src/org/chromium/components/sync/SyncServiceImpl.java
@@ -413,46 +413,83 @@
     interface Natives {
         // Please keep all methods below in the same order as sync_service_android_bridge.h.
         void setSyncRequested(long nativeSyncServiceAndroidBridge);
+
         boolean canSyncFeatureStart(long nativeSyncServiceAndroidBridge);
+
         boolean isSyncFeatureEnabled(long nativeSyncServiceAndroidBridge);
+
         boolean isSyncFeatureActive(long nativeSyncServiceAndroidBridge);
+
         boolean isSyncDisabledByEnterprisePolicy(long nativeSyncServiceAndroidBridge);
+
         boolean isEngineInitialized(long nativeSyncServiceAndroidBridge);
+
         boolean isTransportStateActive(long nativeSyncServiceAndroidBridge);
+
         void setSetupInProgress(long nativeSyncServiceAndroidBridge, boolean inProgress);
+
         boolean isInitialSyncFeatureSetupComplete(long nativeSyncServiceAndroidBridge);
+
         void setInitialSyncFeatureSetupComplete(
                 long nativeSyncServiceAndroidBridge, int syncFirstSetupCompleteSource);
+
         int[] getActiveDataTypes(long nativeSyncServiceAndroidBridge);
+
         int[] getSelectedTypes(long nativeSyncServiceAndroidBridge);
+
         boolean isTypeManagedByPolicy(long nativeSyncServiceAndroidBridge, int type);
+
         boolean isTypeManagedByCustodian(long nativeSyncServiceAndroidBridge, int type);
+
         void setSelectedTypes(long nativeSyncServiceAndroidBridge, boolean syncEverything,
                 int[] userSelectableTypeArray);
+
         boolean isCustomPassphraseAllowed(long nativeSyncServiceAndroidBridge);
+
         boolean isEncryptEverythingEnabled(long nativeSyncServiceAndroidBridge);
+
         boolean isPassphraseRequiredForPreferredDataTypes(long nativeSyncServiceAndroidBridge);
+
         boolean isTrustedVaultKeyRequired(long nativeSyncServiceAndroidBridge);
+
         boolean isTrustedVaultKeyRequiredForPreferredDataTypes(long nativeSyncServiceAndroidBridge);
+
         boolean isTrustedVaultRecoverabilityDegraded(long nativeSyncServiceAndroidBridge);
+
         boolean isUsingExplicitPassphrase(long nativeSyncServiceAndroidBridge);
+
         int getPassphraseType(long nativeSyncServiceAndroidBridge);
+
         void setEncryptionPassphrase(long nativeSyncServiceAndroidBridge, String passphrase);
+
         boolean setDecryptionPassphrase(long nativeSyncServiceAndroidBridge, String passphrase);
+
         long getExplicitPassphraseTime(long nativeSyncServiceAndroidBridge);
+
         void getAllNodes(long nativeSyncServiceAndroidBridge, Callback<JSONArray> callback);
+
         int getAuthError(long nativeSyncServiceAndroidBridge);
+
         boolean hasUnrecoverableError(long nativeSyncServiceAndroidBridge);
+
         boolean requiresClientUpgrade(long nativeSyncServiceAndroidBridge);
+
         @Nullable
         CoreAccountInfo getAccountInfo(long nativeSyncServiceAndroidBridge);
+
         boolean hasSyncConsent(long nativeSyncServiceAndroidBridge);
+
         boolean isPassphrasePromptMutedForCurrentProductVersion(
                 long nativeSyncServiceAndroidBridge);
+
         void markPassphrasePromptMutedForCurrentProductVersion(long nativeSyncServiceAndroidBridge);
+
         boolean hasKeepEverythingSynced(long nativeSyncServiceAndroidBridge);
+
         boolean shouldOfferTrustedVaultOptIn(long nativeSyncServiceAndroidBridge);
+
         void triggerRefresh(long nativeSyncServiceAndroidBridge);
+
         long getLastSyncedTimeForDebugging(long nativeSyncServiceAndroidBridge);
     }
 }
diff --git a/components/sync/base/hash_util.cc b/components/sync/base/hash_util.cc
index 00845ab..9dc69fee 100644
--- a/components/sync/base/hash_util.cc
+++ b/components/sync/base/hash_util.cc
@@ -50,6 +50,8 @@
           {"payment_instrument:",
            base::NumberToString(
                specifics.payment_instrument().instrument_id())});
+    case sync_pb::AutofillWalletSpecifics::MASKED_IBAN:
+      return specifics.masked_iban().instrument_id();
     case sync_pb::AutofillWalletSpecifics::UNKNOWN:
       NOTREACHED();
       return std::string();
diff --git a/components/sync/protocol/autofill_specifics.proto b/components/sync/protocol/autofill_specifics.proto
index bae6f84..b2b7f4a 100644
--- a/components/sync/protocol/autofill_specifics.proto
+++ b/components/sync/protocol/autofill_specifics.proto
@@ -468,6 +468,24 @@
   }
 }
 
+// Contains information of server IBAN (International Bank Account Number).
+message WalletMaskedIban {
+  // Opaque identifier for the account stored in Payments Platform.
+  optional string instrument_id = 1;
+
+  // Prefix of the full IBAN value, if available.
+  optional string prefix = 2;
+
+  // Suffix of the full IBAN value, if available.
+  optional string suffix = 3;
+
+  // Length of the full IBAN value, if available.
+  optional int32 length = 4;
+
+  // Nickname for the IBAN, if available.
+  optional string nickname = 5;
+}
+
 // Details of a bank account required to show it in the payment instrument
 // selector.
 message BankAccountDetails {
@@ -499,6 +517,7 @@
     CUSTOMER_DATA = 3;
     CREDIT_CARD_CLOUD_TOKEN_DATA = 4;
     PAYMENT_INSTRUMENT = 5;
+    MASKED_IBAN = 6;
   }
 
   optional WalletInfoType type = 1;
@@ -523,6 +542,10 @@
   // This field exists if and only if the "type" field equals to
   // PAYMENT_INSTRUMENT.
   optional PaymentInstrument payment_instrument = 6;
+
+  // This field exists if and only if the `type` field equals to
+  // MASKED_IBAN.
+  optional WalletMaskedIban masked_iban = 7;
 }
 
 // Wallet card usage information that can be synced.
@@ -534,13 +557,16 @@
     // TODO(crbug.com/1457187): Some server-side code still relies on this.
     // To keep the protos in sync, it is kept until that code is cleaned up.
     ADDRESS = 2;
+    IBAN = 3;
   }
 
   // The type of the Wallet metadata.
   optional Type type = 1;
 
-  // Base64 encoding of the unique ID string of the corresponding Wallet data.
-  // For Wallet cards, this value is server generated and opaque to Chrome.
+  // Base64 encoding of the unique ID string of the corresponding
+  // AutofillMetadata.
+  // For Wallet cards, this value is WalletMaskedCreditCard::id.
+  // For Wallet IBANs, this value is WalletMaskedIban::instrument_id.
   optional string id = 2;
 
   // The number of times that this Wallet card or address was used.
diff --git a/components/sync/protocol/proto_enum_conversions.cc b/components/sync/protocol/proto_enum_conversions.cc
index 1490503..bba6c9d 100644
--- a/components/sync/protocol/proto_enum_conversions.cc
+++ b/components/sync/protocol/proto_enum_conversions.cc
@@ -66,7 +66,7 @@
 const char* ProtoEnumToString(
     sync_pb::AutofillWalletSpecifics::WalletInfoType wallet_info_type) {
   ASSERT_ENUM_BOUNDS(sync_pb::AutofillWalletSpecifics, WalletInfoType, UNKNOWN,
-                     PAYMENT_INSTRUMENT);
+                     MASKED_IBAN);
   switch (wallet_info_type) {
     ENUM_CASE(sync_pb::AutofillWalletSpecifics, UNKNOWN);
     ENUM_CASE(sync_pb::AutofillWalletSpecifics, MASKED_CREDIT_CARD);
@@ -74,6 +74,7 @@
     ENUM_CASE(sync_pb::AutofillWalletSpecifics, CUSTOMER_DATA);
     ENUM_CASE(sync_pb::AutofillWalletSpecifics, CREDIT_CARD_CLOUD_TOKEN_DATA);
     ENUM_CASE(sync_pb::AutofillWalletSpecifics, PAYMENT_INSTRUMENT);
+    ENUM_CASE(sync_pb::AutofillWalletSpecifics, MASKED_IBAN);
   }
   NOTREACHED();
   return "";
@@ -680,11 +681,12 @@
 
 const char* ProtoEnumToString(
     sync_pb::WalletMetadataSpecifics::Type wallet_metadata_type) {
-  ASSERT_ENUM_BOUNDS(sync_pb::WalletMetadataSpecifics, Type, UNKNOWN, ADDRESS);
+  ASSERT_ENUM_BOUNDS(sync_pb::WalletMetadataSpecifics, Type, UNKNOWN, IBAN);
   switch (wallet_metadata_type) {
     ENUM_CASE(sync_pb::WalletMetadataSpecifics, UNKNOWN);
     ENUM_CASE(sync_pb::WalletMetadataSpecifics, CARD);
     ENUM_CASE(sync_pb::WalletMetadataSpecifics, ADDRESS);
+    ENUM_CASE(sync_pb::WalletMetadataSpecifics, IBAN);
   }
   NOTREACHED();
   return "";
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index 811e17e..8627d6b 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -1560,6 +1560,14 @@
   VISIT(instrument_token);
 }
 
+VISIT_PROTO_FIELDS(const sync_pb::WalletMaskedIban& proto) {
+  VISIT(instrument_id);
+  VISIT(prefix);
+  VISIT(suffix);
+  VISIT(length);
+  VISIT(nickname);
+}
+
 VISIT_PROTO_FIELDS(const sync_pb::WebApkIconInfo& proto) {
   VISIT(size_in_px);
   VISIT(url);
diff --git a/components/user_education/common/product_messaging_controller.cc b/components/user_education/common/product_messaging_controller.cc
index 787cbb8..0e428a4 100644
--- a/components/user_education/common/product_messaging_controller.cc
+++ b/components/user_education/common/product_messaging_controller.cc
@@ -6,6 +6,7 @@
 
 #include <sstream>
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
 #include "base/functional/callback.h"
diff --git a/components/viz/common/resources/shared_image_format.cc b/components/viz/common/resources/shared_image_format.cc
index c27d7ae..310ee1d 100644
--- a/components/viz/common/resources/shared_image_format.cc
+++ b/components/viz/common/resources/shared_image_format.cc
@@ -132,6 +132,10 @@
   switch (subsampling) {
     case SharedImageFormat::Subsampling::k420:
       return "420";
+    case SharedImageFormat::Subsampling::k422:
+      return "422";
+    case SharedImageFormat::Subsampling::k444:
+      return "444";
   }
 }
 
@@ -292,30 +296,30 @@
     return size;
   }
 
-  switch (plane_config()) {
-    case PlaneConfig::kY_U_V:
-    case PlaneConfig::kY_V_U:
-      if (plane_index == 0) {
-        return size;
-      } else {
-        DCHECK_EQ(subsampling(), Subsampling::k420);
-        return gfx::ScaleToCeiledSize(size, 0.5);
-      }
-    case PlaneConfig::kY_UV:
-      if (plane_index == 1) {
-        DCHECK_EQ(subsampling(), Subsampling::k420);
-        return gfx::ScaleToCeiledSize(size, 0.5);
-      } else {
-        return size;
-      }
-    case PlaneConfig::kY_UV_A:
-      if (plane_index == 1) {
-        DCHECK_EQ(subsampling(), Subsampling::k420);
-        return gfx::ScaleToCeiledSize(size, 0.5);
-      } else {
-        return size;
-      }
+  // Y plane is always size
+  if (plane_index == 0) {
+    return size;
   }
+  // A plane is always size
+  if (plane_config() == PlaneConfig::kY_UV_A && plane_index == 2) {
+    return size;
+  }
+
+  // UV scales
+  float width_scale = 1.0;
+  float height_scale = 1.0;
+  switch (subsampling()) {
+    case Subsampling::k420:
+      width_scale = 0.5;
+      height_scale = 0.5;
+      break;
+    case Subsampling::k422:
+      width_scale = 0.5;
+      break;
+    case Subsampling::k444:
+      break;
+  }
+  return gfx::ScaleToCeiledSize(size, width_scale, height_scale);
 }
 
 // For multiplanar formats.
diff --git a/components/viz/common/resources/shared_image_format.h b/components/viz/common/resources/shared_image_format.h
index 6d97e85..a4f87cf 100644
--- a/components/viz/common/resources/shared_image_format.h
+++ b/components/viz/common/resources/shared_image_format.h
@@ -51,6 +51,8 @@
   // and V). If alpha is present it is not subsampled.
   enum class Subsampling : uint8_t {
     k420,  // 1 set of UV values for each 2x2 block of Y values.
+    k422,  // 1 set of UV values for each 2x1 block of Y values.
+    k444,  // No subsampling. UV values for each Y.
   };
 
   // Specifies the channel format for Y plane in the YUV (and optionally A)
diff --git a/components/viz/common/resources/shared_image_format_unittest.cc b/components/viz/common/resources/shared_image_format_unittest.cc
index acedecb..b9712dc0 100644
--- a/components/viz/common/resources/shared_image_format_unittest.cc
+++ b/components/viz/common/resources/shared_image_format_unittest.cc
@@ -70,11 +70,59 @@
   EXPECT_EQ(format.EstimatedSizeInBytes(kDefaultSize), 15000u);
 
   // Y: 9 bytes per row (1 channel * 1 byte * 9 width) * 9 rows = 81 bytes.
-  // V: 5 bytes per row (1 channels * 1 byte * 5 width) * 5 rows = 25 bytes.
   // U: 5 bytes per row (1 channels * 1 byte * 5 width) * 5 rows = 25 bytes.
+  // V: 5 bytes per row (1 channels * 1 byte * 5 width) * 5 rows = 25 bytes.
   EXPECT_EQ(format.EstimatedSizeInBytes(kOddSize), 131u);
 }
 
+TEST_F(SharedImageFormatTest, MultiPlaneI422) {
+  // 8-bit 4:2:2 Y_U_V format (I422)
+  SharedImageFormat format =
+      SharedImageFormat::MultiPlane(SharedImageFormat::PlaneConfig::kY_U_V,
+                                    SharedImageFormat::Subsampling::k422,
+                                    SharedImageFormat::ChannelFormat::k8);
+  // Test for NumChannelsInPlane
+  std::vector<int> expected_channels = {1, 1, 1};
+  TestNumChannelsInPlane(expected_channels, format);
+
+  // Y: 100 bytes per row (1 channel * 1 byte * 100 width) * 100 rows = 10000
+  // bytes.
+  // U: 50 bytes per row (1 channel * 1 byte * 50 width) * 100 rows = 5000
+  // bytes.
+  // V: 50 bytes per row (1 channel * 1 byte * 50 width) * 100 rows = 5000
+  // bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kDefaultSize), 20000u);
+
+  // Y: 9 bytes per row (1 channel * 1 byte * 9 width) * 9 rows = 81 bytes.
+  // U: 5 bytes per row (1 channel * 1 byte * 5 width) * 9 rows = 45 bytes.
+  // V: 5 bytes per row (1 channel * 1 byte * 5 width) * 9 rows = 45 bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kOddSize), 171u);
+}
+
+TEST_F(SharedImageFormatTest, MultiPlaneI444) {
+  // 8-bit 4:2:2 Y_U_V format (I444)
+  SharedImageFormat format =
+      SharedImageFormat::MultiPlane(SharedImageFormat::PlaneConfig::kY_U_V,
+                                    SharedImageFormat::Subsampling::k444,
+                                    SharedImageFormat::ChannelFormat::k8);
+  // Test for NumChannelsInPlane
+  std::vector<int> expected_channels = {1, 1, 1};
+  TestNumChannelsInPlane(expected_channels, format);
+
+  // Y: 100 bytes per row (1 channel * 1 byte * 100 width) * 100 rows = 10000
+  // bytes.
+  // U: 100 bytes per row (1 channel * 1 byte * 100 width) * 100 rows = 10000
+  // bytes.
+  // V: 100 bytes per row (1 channel * 1 byte * 100 width) * 100 rows = 10000
+  // bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kDefaultSize), 30000u);
+
+  // Y: 9 bytes per row (1 channel * 1 byte * 9 width) * 9 rows = 81 bytes.
+  // U: 9 bytes per row (1 channel * 1 byte * 9 width) * 9 rows = 81 bytes.
+  // V: 9 bytes per row (1 channel * 1 byte * 9 width) * 9 rows = 81 bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kOddSize), 243u);
+}
+
 TEST_F(SharedImageFormatTest, MultiPlaneP010) {
   // 10-bit 4:2:0 Y_UV biplanar format (P010)
   SharedImageFormat format = MultiPlaneFormat::kP010;
@@ -124,6 +172,78 @@
   EXPECT_EQ(format.EstimatedSizeInBytes(kOddSize), 424u);
 }
 
+TEST_F(SharedImageFormatTest, MultiPlaneYUV420P10) {
+  // 10-bit float 4:2:0 Y_U_V format (YUV420P10)
+  SharedImageFormat format =
+      SharedImageFormat::MultiPlane(SharedImageFormat::PlaneConfig::kY_U_V,
+                                    SharedImageFormat::Subsampling::k420,
+                                    SharedImageFormat::ChannelFormat::k10);
+  // Test for NumChannelsInPlane
+  std::vector<int> expected_channels = {1, 1, 1};
+  TestNumChannelsInPlane(expected_channels, format);
+
+  // Y: 200 bytes per row (1 channel * 2 bytes * 100 width) * 100 rows = 20000
+  // bytes.
+  // U: 100 bytes per row (1 channel * 2 bytes * 50 width) * 50 rows = 5000
+  // bytes.
+  // V: 100 bytes per row (1 channel * 2 bytes * 50 width) * 50 rows = 5000
+  // bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kDefaultSize), 30000u);
+
+  // Y: 18 bytes per row (1 channel * 2 bytes * 9 width) * 9 rows = 162 bytes.
+  // U: 10 bytes per row (1 channel * 2 bytes * 5 width) * 5 rows = 50 bytes.
+  // V: 10 bytes per row (1 channel * 2 bytes * 5 width) * 5 rows = 50 bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kOddSize), 262u);
+}
+
+TEST_F(SharedImageFormatTest, MultiPlaneYUV422P10) {
+  // 10-bit float 4:2:2 Y_U_V format (YUV422P10)
+  SharedImageFormat format =
+      SharedImageFormat::MultiPlane(SharedImageFormat::PlaneConfig::kY_U_V,
+                                    SharedImageFormat::Subsampling::k422,
+                                    SharedImageFormat::ChannelFormat::k10);
+  // Test for NumChannelsInPlane
+  std::vector<int> expected_channels = {1, 1, 1};
+  TestNumChannelsInPlane(expected_channels, format);
+
+  // Y: 200 bytes per row (1 channel * 2 bytes * 100 width) * 100 rows = 20000
+  // bytes.
+  // U: 100 bytes per row (1 channel * 2 bytes * 50 width) * 100 rows = 10000
+  // bytes.
+  // V: 100 bytes per row (1 channel * 2 bytes * 50 width) * 100 rows = 10000
+  // bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kDefaultSize), 40000u);
+
+  // Y: 18 bytes per row (1 channel * 2 bytes * 9 width) * 9 rows = 162 bytes.
+  // U: 10 bytes per row (1 channel * 2 bytes * 5 width) * 9 rows = 90 bytes.
+  // V: 10 bytes per row (1 channel * 2 bytes * 5 width) * 9 rows = 90 bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kOddSize), 342u);
+}
+
+TEST_F(SharedImageFormatTest, MultiPlaneYUV444P10) {
+  // 10-bit float 4:4:4 Y_U_V format (YUV444P10)
+  SharedImageFormat format =
+      SharedImageFormat::MultiPlane(SharedImageFormat::PlaneConfig::kY_U_V,
+                                    SharedImageFormat::Subsampling::k444,
+                                    SharedImageFormat::ChannelFormat::k10);
+  // Test for NumChannelsInPlane
+  std::vector<int> expected_channels = {1, 1, 1};
+  TestNumChannelsInPlane(expected_channels, format);
+
+  // Y: 200 bytes per row (1 channel * 2 bytes * 100 width) * 100 rows = 20000
+  // bytes.
+  // U: 200 bytes per row (1 channel * 2 bytes * 100 width) * 100 rows = 20000
+  // bytes.
+  // V: 200 bytes per row (1 channel * 2 bytes * 100 width) * 100 rows = 20000
+  // bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kDefaultSize), 60000u);
+
+  // Y: 18 bytes per row (1 channel * 2 bytes * 9 width) * 9 rows = 162 bytes.
+  // U: 18 bytes per row (1 channel * 2 bytes * 9 width) * 9 rows = 162 bytes.
+  // V: 18 bytes per row (1 channel * 2 bytes * 9 width) * 9 rows = 162 bytes.
+  EXPECT_EQ(format.EstimatedSizeInBytes(kOddSize), 486u);
+}
+
 TEST_F(SharedImageFormatTest, SinglePlaneRGBA_8888) {
   auto format = SinglePlaneFormat::kRGBA_8888;
   EXPECT_EQ(1, format.NumberOfPlanes());
diff --git a/components/viz/service/display_embedder/compositor_gpu_thread.cc b/components/viz/service/display_embedder/compositor_gpu_thread.cc
index dae2c9c..ff1ff9c 100644
--- a/components/viz/service/display_embedder/compositor_gpu_thread.cc
+++ b/components/viz/service/display_embedder/compositor_gpu_thread.cc
@@ -128,12 +128,9 @@
   const bool use_passthrough_decoder =
       gpu::gles2::PassthroughCommandDecoderSupported() &&
       gpu_preferences.use_passthrough_cmd_decoder;
-  gpu::ContextCreationAttribs attribs_helper;
-  attribs_helper.context_type = features::UseGles2ForOopR()
-                                    ? gpu::CONTEXT_TYPE_OPENGLES2
-                                    : gpu::CONTEXT_TYPE_OPENGLES3;
-  gl::GLContextAttribs attribs = gpu::gles2::GenerateGLContextAttribs(
-      attribs_helper, use_passthrough_decoder);
+  gl::GLContextAttribs attribs =
+      gpu::gles2::GenerateGLContextAttribsForCompositor(
+          use_passthrough_decoder);
   attribs.angle_context_virtualization_group_number =
       gl::AngleContextVirtualizationGroup::kDrDc;
 
diff --git a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidatorTest.java b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidatorTest.java
index 3a17713f..a3fb46a 100644
--- a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidatorTest.java
+++ b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidatorTest.java
@@ -415,8 +415,6 @@
             "bad-sig.apk",
             "bad-utf8-fname.apk",
             "empty.apk",
-            "extra-field-too-large.apk",
-            "extra-len-too-large.apk",
             "fcomment-too-large.apk",
             "no-cd.apk",
             "no-comment.apk",
diff --git a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkVerifySignature.java b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkVerifySignature.java
index 51c9cc8..c5c73b8 100644
--- a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkVerifySignature.java
+++ b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkVerifySignature.java
@@ -88,12 +88,6 @@
     /** Maximum file comment length permitted. */
     private static final int MAX_FILE_COMMENT_LENGTH = 0;
 
-    /**
-     * Maximum extra field length permitted.
-     * Support .so alignment and a 64 bytes bytes for any extras.
-     */
-    private static final int MAX_EXTRA_LENGTH = 4096 + 64;
-
     /** The memory buffer we are going to read the zip from. */
     private final ByteBuffer mBuffer;
 
@@ -328,9 +322,6 @@
             int offset = read4();
             String filename = readString(fileNameLength);
             seekDelta(extraLen + fileCommentLength);
-            if (extraLen > MAX_EXTRA_LENGTH) {
-                return Error.EXTRA_FIELD_TOO_LARGE;
-            }
             if (fileCommentLength > MAX_FILE_COMMENT_LENGTH) {
                 return Error.FILE_COMMENT_TOO_LARGE;
             }
@@ -366,9 +357,6 @@
             seekDelta(18);
             int fileNameLength = read2();
             int extraFieldLength = read2();
-            if (extraFieldLength > MAX_EXTRA_LENGTH) {
-                return Error.EXTRA_FIELD_TOO_LARGE;
-            }
 
             block.mHeaderSize =
                     (mBuffer.position() - block.mPosition) + fileNameLength + extraFieldLength;
diff --git a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkVerifySignatureTest.java b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkVerifySignatureTest.java
index 94ce293..9e89b86 100644
--- a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkVerifySignatureTest.java
+++ b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkVerifySignatureTest.java
@@ -93,8 +93,6 @@
             new FileResult("bad-sig.apk", Error.INCORRECT_SIGNATURE),
             new FileResult("bad-utf8-fname.apk", Error.INCORRECT_SIGNATURE),
             new FileResult("empty.apk", Error.BAD_APK),
-            new FileResult("extra-field-too-large.apk", Error.EXTRA_FIELD_TOO_LARGE),
-            new FileResult("extra-len-too-large.apk", Error.BAD_APK),
             new FileResult("no-cd.apk", Error.BAD_APK),
             new FileResult("no-comment.apk", Error.SIGNATURE_NOT_FOUND),
             new FileResult("no-eocd.apk", Error.BAD_APK),
diff --git a/components/webapps/browser/features.cc b/components/webapps/browser/features.cc
index 6b40c2d..4411b3f 100644
--- a/components/webapps/browser/features.cc
+++ b/components/webapps/browser/features.cc
@@ -134,11 +134,5 @@
 
 extern const base::FeatureParam<int> kMinimumFaviconSize{&kUniversalInstallIcon,
                                                          "size", 48};
-
-// Enables per PWA System Media Controls on Windows
-BASE_FEATURE(kWebAppSystemMediaControlsWin,
-             "WebAppSystemMediaControlsWin",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 }  // namespace features
 }  // namespace webapps
diff --git a/components/webapps/browser/features.h b/components/webapps/browser/features.h
index e34d27f..4d81571 100644
--- a/components/webapps/browser/features.h
+++ b/components/webapps/browser/features.h
@@ -67,8 +67,6 @@
 BASE_DECLARE_FEATURE(kUniversalInstallIcon);
 extern const base::FeatureParam<int> kMinimumFaviconSize;
 
-BASE_DECLARE_FEATURE(kWebAppSystemMediaControlsWin);
-
 }  // namespace features
 }  // namespace webapps
 
diff --git a/content/browser/attribution_reporting/attribution_internals_browsertest.cc b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
index 0c70d872..915c5f99 100644
--- a/content/browser/attribution_reporting/attribution_internals_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
@@ -951,8 +951,6 @@
 
 IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUISendReports_ReportsRemoved) {
-  ASSERT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
-
   EXPECT_CALL(*manager(), GetPendingReportsForInternalUse)
       .WillOnce(RunOnceCallback<1>(std::vector<AttributionReport>{
           ReportBuilder(AttributionInfoBuilder().Build(),
@@ -967,6 +965,8 @@
       .WillOnce([](const std::vector<AttributionReport::Id>& ids,
                    base::OnceClosure done) { std::move(done).Run(); });
 
+  ASSERT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
+
   static constexpr char kScript[] = R"(
     const table = document.querySelector('#reportTable')
         .shadowRoot.querySelector('tbody');
@@ -990,7 +990,6 @@
 
   // Wait for the table to rendered.
   TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
-  ClickRefreshButton();
   ASSERT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
 
   // Click the send reports button and expect that the report table is emptied.
@@ -1001,9 +1000,8 @@
   ASSERT_TRUE(ExecJsInWebUI(R"(
     document.querySelector('#reportTable')
      .shadowRoot.querySelector('input[type="checkbox"]').click();
+    document.getElementById('send-reports').click();
   )"));
-  ASSERT_TRUE(
-      ExecJsInWebUI("document.getElementById('send-reports').click();"));
 
   // The real manager would do this itself, but the test manager requires manual
   // triggering.
@@ -1284,8 +1282,6 @@
 
 IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUISendAggregatableReports_ReportsRemoved) {
-  ASSERT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
-
   EXPECT_CALL(*manager(), GetPendingReportsForInternalUse)
       .WillOnce(RunOnceCallback<1>(std::vector<AttributionReport>{
           ReportBuilder(AttributionInfoBuilder().Build(),
@@ -1301,6 +1297,8 @@
       .WillOnce([](const std::vector<AttributionReport::Id>& ids,
                    base::OnceClosure done) { std::move(done).Run(); });
 
+  ASSERT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
+
   static constexpr char kScript[] = R"(
     const table = document.querySelector('#aggregatableReportTable')
         .shadowRoot.querySelector('tbody');
@@ -1324,7 +1322,6 @@
 
   // Wait for the table to rendered.
   TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
-  ClickRefreshButton();
   ASSERT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
 
   // Click the send reports button and expect that the report table is emptied.
@@ -1334,14 +1331,21 @@
   static constexpr char kObserveEmptyReportsTableScript[] = R"(
     const table = document.querySelector('#aggregatableReportTable')
         .shadowRoot.querySelector('tbody');
-    const obs = new MutationObserver((_, obs) => {
+    const setTitleIfDone = (_, obs) => {
       if (table.children.length === 1 &&
           table.children[0].children[0]?.innerText === 'No sent or pending reports.') {
-        obs.disconnect();
+        if (obs) {
+          obs.disconnect();
+        }
         document.title = $1;
+        return true;
       }
-    });
-    obs.observe(table, {childList: true, subtree: true, characterData: true});
+      return false;
+    };
+    if (!setTitleIfDone()) {
+      const obs = new MutationObserver(setTitleIfDone);
+      obs.observe(table, {childList: true, subtree: true, characterData: true});
+    }
   )";
   ASSERT_TRUE(
       ExecJsInWebUI(JsReplace(kObserveEmptyReportsTableScript, kSentTitle)));
diff --git a/content/browser/attribution_reporting/privacy_sandbox_ads_apis_browsertest.cc b/content/browser/attribution_reporting/privacy_sandbox_ads_apis_browsertest.cc
index 76d8d91..08dab61 100644
--- a/content/browser/attribution_reporting/privacy_sandbox_ads_apis_browsertest.cc
+++ b/content/browser/attribution_reporting/privacy_sandbox_ads_apis_browsertest.cc
@@ -144,7 +144,7 @@
   PrivacySandboxAdsAPIsAllEnabledBrowserTest() {
     feature_list_.InitWithFeatures(
         {blink::features::kPrivacySandboxAdsAPIs,
-         blink::features::kBrowsingTopics, blink::features::kBrowsingTopicsXHR,
+         blink::features::kBrowsingTopics,
          blink::features::kBrowsingTopicsDocumentAPI,
          blink::features::kInterestGroupStorage, blink::features::kFencedFrames,
          blink::features::kSharedStorageAPI},
@@ -239,63 +239,6 @@
   EXPECT_FALSE(last_topics_header());
 }
 
-IN_PROC_BROWSER_TEST_F(PrivacySandboxAdsAPIsAllEnabledBrowserTest,
-                       OriginTrialEnabled_TopicsAllowedForXhr) {
-  EXPECT_TRUE(NavigateToURL(
-      shell(), GURL("https://example.test/page_with_ads_apis_ot.html")));
-
-  EXPECT_EQ("success", EvalJs(shell()->web_contents(),
-                              R"(
-    const xhr = new XMLHttpRequest();
-
-    const completePromise = new Promise(resolve => {
-      xhr.onreadystatechange = function() {
-        if (xhr.readyState == XMLHttpRequest.DONE) {
-          resolve('success');
-        }
-      }
-    });
-
-    xhr.open('GET', 'https://example.test/page_without_ads_apis_ot.html');
-    xhr.deprecatedBrowsingTopics = true;
-    xhr.send();
-    completePromise;
-    )"));
-
-  EXPECT_TRUE(last_request_is_topics_request());
-  EXPECT_TRUE(last_topics_header());
-  EXPECT_EQ(last_topics_header().value(),
-            "(1);v=chrome.1:1:2, ();p=P00000000000");
-}
-
-IN_PROC_BROWSER_TEST_F(PrivacySandboxAdsAPIsAllEnabledBrowserTest,
-                       OriginTrialDisabled_TopicsNotAllowedForXhr) {
-  // Navigate to a page without an OT token.
-  EXPECT_TRUE(NavigateToURL(
-      shell(), GURL("https://example.test/page_without_ads_apis_ot.html")));
-
-  EXPECT_EQ("success", EvalJs(shell()->web_contents(),
-                              R"(
-    const xhr = new XMLHttpRequest();
-
-    const completePromise = new Promise(resolve => {
-      xhr.onreadystatechange = function() {
-        if (xhr.readyState == XMLHttpRequest.DONE) {
-          resolve('success');
-        }
-      }
-    });
-
-    xhr.open('GET', 'https://example.test/page_without_ads_apis_ot.html');
-    xhr.deprecatedBrowsingTopics = true;
-    xhr.send();
-    completePromise;
-    )"));
-
-  EXPECT_FALSE(last_request_is_topics_request());
-  EXPECT_FALSE(last_topics_header());
-}
-
 IN_PROC_BROWSER_TEST_F(
     PrivacySandboxAdsAPIsAllEnabledBrowserTest,
     OriginTrialEnabled_HasBrowsingTopicsIframeAttr_ConsideredForEmbedderOptIn) {
@@ -352,7 +295,6 @@
   PrivacySandboxAdsAPIsTopicsDisabledBrowserTest() {
     feature_list_.InitWithFeatures(
         /*enabled_features=*/{blink::features::kPrivacySandboxAdsAPIs,
-                              blink::features::kBrowsingTopicsXHR,
                               blink::features::kBrowsingTopicsDocumentAPI},
         /*disabled_features=*/{blink::features::kBrowsingTopics});
   }
@@ -391,48 +333,21 @@
   EXPECT_FALSE(last_topics_header());
 }
 
-IN_PROC_BROWSER_TEST_F(PrivacySandboxAdsAPIsTopicsDisabledBrowserTest,
-                       OriginTrialEnabled_TopicsNotAllowedForXhr) {
-  EXPECT_TRUE(NavigateToURL(
-      shell(), GURL("https://example.test/page_with_ads_apis_ot.html")));
-
-  EXPECT_EQ("success", EvalJs(shell()->web_contents(),
-                              R"(
-    const xhr = new XMLHttpRequest();
-
-    const completePromise = new Promise(resolve => {
-      xhr.onreadystatechange = function() {
-        if (xhr.readyState == XMLHttpRequest.DONE) {
-          resolve('success');
-        }
-      }
-    });
-
-    xhr.open('GET', 'https://example.test/page_without_ads_apis_ot.html');
-    xhr.deprecatedBrowsingTopics = true;
-    xhr.send();
-    completePromise;
-    )"));
-
-  EXPECT_FALSE(last_request_is_topics_request());
-  EXPECT_FALSE(last_topics_header());
-}
-
-class PrivacySandboxAdsAPIsTopicsXHRDisabledBrowserTest
+class PrivacySandboxAdsAPIsTopicsBrowserTest
     : public PrivacySandboxAdsAPIsBrowserTestBase {
  public:
-  PrivacySandboxAdsAPIsTopicsXHRDisabledBrowserTest() {
+  PrivacySandboxAdsAPIsTopicsBrowserTest() {
     feature_list_.InitWithFeatures(
         /*enabled_features=*/{blink::features::kPrivacySandboxAdsAPIs,
                               blink::features::kBrowsingTopics},
-        /*disabled_features=*/{blink::features::kBrowsingTopicsXHR});
+        /*disabled_features=*/{});
   }
 
  private:
   base::test::ScopedFeatureList feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(PrivacySandboxAdsAPIsTopicsXHRDisabledBrowserTest,
+IN_PROC_BROWSER_TEST_F(PrivacySandboxAdsAPIsTopicsBrowserTest,
                        OriginTrialEnabled_TopicsFeatureDetected) {
   EXPECT_TRUE(NavigateToURL(
       shell(), GURL("https://example.test/page_with_ads_apis_ot.html")));
@@ -444,7 +359,7 @@
   EXPECT_EQ(true, EvalJs(shell(), "document.browsingTopics !== undefined"));
 }
 
-IN_PROC_BROWSER_TEST_F(PrivacySandboxAdsAPIsTopicsXHRDisabledBrowserTest,
+IN_PROC_BROWSER_TEST_F(PrivacySandboxAdsAPIsTopicsBrowserTest,
                        OriginTrialEnabled_TopicsAllowedForFetch) {
   EXPECT_TRUE(NavigateToURL(
       shell(), GURL("https://example.test/page_with_ads_apis_ot.html")));
@@ -461,40 +376,12 @@
             "(1);v=chrome.1:1:2, ();p=P00000000000");
 }
 
-IN_PROC_BROWSER_TEST_F(PrivacySandboxAdsAPIsTopicsXHRDisabledBrowserTest,
-                       OriginTrialEnabled_TopicsNotAllowedForXhr) {
-  EXPECT_TRUE(NavigateToURL(
-      shell(), GURL("https://example.test/page_with_ads_apis_ot.html")));
-
-  EXPECT_EQ("success", EvalJs(shell()->web_contents(),
-                              R"(
-    const xhr = new XMLHttpRequest();
-
-    const completePromise = new Promise(resolve => {
-      xhr.onreadystatechange = function() {
-        if (xhr.readyState == XMLHttpRequest.DONE) {
-          resolve('success');
-        }
-      }
-    });
-
-    xhr.open('GET', 'https://example.test/page_without_ads_apis_ot.html');
-    xhr.deprecatedBrowsingTopics = true;
-    xhr.send();
-    completePromise;
-    )"));
-
-  EXPECT_FALSE(last_request_is_topics_request());
-  EXPECT_FALSE(last_topics_header());
-}
-
 class PrivacySandboxAdsAPIsTopicsDocumentAPIDisabledBrowserTest
     : public PrivacySandboxAdsAPIsBrowserTestBase {
  public:
   PrivacySandboxAdsAPIsTopicsDocumentAPIDisabledBrowserTest() {
     feature_list_.InitWithFeatures(
         /*enabled_features=*/{blink::features::kPrivacySandboxAdsAPIs,
-                              blink::features::kBrowsingTopicsXHR,
                               blink::features::kBrowsingTopics},
         /*disabled_features=*/{blink::features::kBrowsingTopicsDocumentAPI});
   }
diff --git a/content/browser/indexed_db/indexed_db_factory.cc b/content/browser/indexed_db/indexed_db_factory.cc
index 185eeb72..4752154 100644
--- a/content/browser/indexed_db/indexed_db_factory.cc
+++ b/content/browser/indexed_db/indexed_db_factory.cc
@@ -362,9 +362,14 @@
   // will be lost.
   std::tie(bucket_context_handle, s, error, std::ignore, std::ignore) =
       GetOrCreateBucketContext(*bucket, context_->GetDataPath(bucket_locator),
-                               /*create_if_missing=*/true);
+                               /*create_if_missing=*/false);
   if (!bucket_context_handle.IsHeld() ||
       !bucket_context_handle.bucket_context()) {
+    if (s.IsNotFound()) {
+      factory_client->OnDeleteSuccess(/*version=*/0);
+      return;
+    }
+
     factory_client->OnError(error);
     if (s.IsCorruption()) {
       HandleBackingStoreCorruption(bucket_locator, error);
@@ -407,8 +412,7 @@
   }
 
   if (!base::Contains(names, name)) {
-    const int64_t version = 0;
-    factory_client->OnDeleteSuccess(version);
+    factory_client->OnDeleteSuccess(/*version=*/0);
     return;
   }
 
@@ -689,6 +693,10 @@
     if (LIKELY(s.ok())) {
       break;
     }
+    if (!create_if_missing && s.IsNotFound()) {
+      return {IndexedDBBucketContextHandle(), s, IndexedDBDatabaseError(),
+              data_loss_info, /*was_cold_open=*/true};
+    }
     DCHECK(!backing_store);
     // If the disk is full, always exit immediately.
     if (disk_full) {
diff --git a/content/browser/indexed_db/indexed_db_factory_unittest.cc b/content/browser/indexed_db/indexed_db_factory_unittest.cc
index b34041aff..4c7d45a 100644
--- a/content/browser/indexed_db/indexed_db_factory_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_factory_unittest.cc
@@ -729,21 +729,57 @@
                        factory_remote.BindNewPipeAndPassReceiver(),
                        ToBucketInfo(bucket_locator));
 
-  // Delete db.
-  MockMojoIndexedDBFactoryClient client;
-  MockMojoIndexedDBDatabaseCallbacks database_callbacks;
-  base::RunLoop run_loop;
-  EXPECT_CALL(client, DeleteSuccess)
-      .WillOnce(
-          testing::DoAll(::base::test::RunClosure(run_loop.QuitClosure())));
-  mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
-  factory_remote->DeleteDatabase(client.CreateInterfacePtrAndBind(), u"db",
-                                 /*force_close=*/false);
-  run_loop.Run();
+  // Don't create a backing store if one doesn't exist.
+  {
+    // Delete db.
+    MockMojoIndexedDBFactoryClient client;
+    MockMojoIndexedDBDatabaseCallbacks database_callbacks;
+    base::RunLoop run_loop;
+    EXPECT_CALL(client, DeleteSuccess)
+        .WillOnce(
+            testing::DoAll(::base::test::RunClosure(run_loop.QuitClosure())));
+    mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
+    factory_remote->DeleteDatabase(client.CreateInterfacePtrAndBind(), u"db",
+                                   /*force_close=*/false);
+    run_loop.Run();
 
-  // Since there are no more references the factory should be closing.
-  EXPECT_TRUE(factory()->GetBucketContext(bucket_locator.id));
-  EXPECT_TRUE(factory()->GetBucketContext(bucket_locator.id)->IsClosing());
+    // Backing store shouldn't exist.
+    EXPECT_FALSE(factory()->GetBucketContext(bucket_locator.id));
+  }
+
+  // Now create a database and thus the backing store.
+  {
+    MockMojoIndexedDBFactoryClient client;
+    MockMojoIndexedDBDatabaseCallbacks database_callbacks;
+    base::RunLoop run_loop;
+    EXPECT_CALL(client, MockedOpenSuccess)
+        .WillOnce(::base::test::RunClosure(run_loop.QuitClosure()));
+    mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
+    factory_remote->Open(client.CreateInterfacePtrAndBind(),
+                         database_callbacks.CreateInterfacePtrAndBind(), u"db",
+                         /*version=*/0,
+                         transaction_remote.BindNewEndpointAndPassReceiver(),
+                         /*transaction_id=*/1);
+    run_loop.Run();
+  }
+
+  // Delete the database now that the backing store actually exists.
+  {
+    MockMojoIndexedDBFactoryClient client;
+    MockMojoIndexedDBDatabaseCallbacks database_callbacks;
+    base::RunLoop run_loop;
+    EXPECT_CALL(client, DeleteSuccess)
+        .WillOnce(
+            testing::DoAll(::base::test::RunClosure(run_loop.QuitClosure())));
+    mojo::AssociatedRemote<blink::mojom::IDBTransaction> transaction_remote;
+    factory_remote->DeleteDatabase(client.CreateInterfacePtrAndBind(), u"db",
+                                   /*force_close=*/false);
+    run_loop.Run();
+
+    // Since there are no more references the factory should be closing.
+    ASSERT_TRUE(factory()->GetBucketContext(bucket_locator.id));
+    EXPECT_TRUE(factory()->GetBucketContext(bucket_locator.id)->IsClosing());
+  }
 }
 
 TEST_F(IndexedDBFactoryTest, GetDatabaseNames_NoFactory) {
diff --git a/content/browser/interest_group/interest_group_caching_storage.cc b/content/browser/interest_group/interest_group_caching_storage.cc
index 000bf41..6ee9740 100644
--- a/content/browser/interest_group/interest_group_caching_storage.cc
+++ b/content/browser/interest_group/interest_group_caching_storage.cc
@@ -8,6 +8,7 @@
 #include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "content/browser/interest_group/interest_group_manager_impl.h"
@@ -94,8 +95,12 @@
         FROM_HERE, base::BindOnce(std::move(callback),
                                   scoped_refptr<StorageInterestGroups>(
                                       cached_groups_it->second.get())));
+    base::UmaHistogramBoolean("Ads.InterestGroup.Auction.LoadGroupsCacheHit",
+                              true);
     return;
   }
+  base::UmaHistogramBoolean("Ads.InterestGroup.Auction.LoadGroupsCacheHit",
+                            false);
 
   // If there is no cache hit, run
   // InterestGroupStorage::GetInterestGroupsForOwner only if there are no
diff --git a/content/browser/interest_group/interest_group_caching_storage_unittest.cc b/content/browser/interest_group/interest_group_caching_storage_unittest.cc
index 9f54bf5..8e56b39 100644
--- a/content/browser/interest_group/interest_group_caching_storage_unittest.cc
+++ b/content/browser/interest_group/interest_group_caching_storage_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/files/scoped_temp_dir.h"
 #include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
@@ -247,7 +248,6 @@
   loaded_igs = GetInterestGroupsForOwner(caching_storage.get(), owner);
   ASSERT_EQ(loaded_igs->get()->size(), 2u);
   ASSERT_NE(loaded_igs->get(), previously_loaded_igs->get());
-
   previously_loaded_igs = loaded_igs;
 
   // Leaving an interest group updates the cached value.
@@ -266,7 +266,6 @@
   loaded_igs = GetInterestGroupsForOwner(caching_storage.get(), owner);
   ASSERT_EQ(loaded_igs->get()->size(), 1u);
   ASSERT_NE(loaded_igs->get(), previously_loaded_igs->get());
-
   previously_loaded_igs = loaded_igs;
 
   InterestGroupUpdate ig_update;
@@ -553,4 +552,43 @@
   ASSERT_NE(loaded_igs->get(), loaded_igs_again->get());
 }
 
+TEST_F(InterestGroupCachingStorageTest, LoadGroupsCacheHitHistogram) {
+  std::unique_ptr<content::InterestGroupCachingStorage> caching_storage =
+      CreateCachingStorage();
+  url::Origin owner = url::Origin::Create(GURL("https://www.example.com/"));
+  auto ig = MakeInterestGroup(owner, "name");
+
+  JoinInterestGroup(caching_storage.get(), ig, GURL("https://www.test.com"));
+
+  base::HistogramTester histogram_tester;
+  GetInterestGroupsForOwner(caching_storage.get(), owner);
+  histogram_tester.ExpectUniqueSample(
+      "Ads.InterestGroup.Auction.LoadGroupsCacheHit", false, 1);
+
+  absl::optional<scoped_refptr<StorageInterestGroups>> loaded_igs =
+      GetInterestGroupsForOwner(caching_storage.get(), owner);
+  // Not a cache hit because the previous result of GetInterestGroupsForOwner is
+  // not in memory.
+  histogram_tester.ExpectBucketCount(
+      "Ads.InterestGroup.Auction.LoadGroupsCacheHit", false, 2);
+
+  GetInterestGroupsForOwner(caching_storage.get(), owner);
+  histogram_tester.ExpectBucketCount(
+      "Ads.InterestGroup.Auction.LoadGroupsCacheHit", true, 1);
+
+  GetInterestGroupsForOwner(caching_storage.get(), owner);
+  histogram_tester.ExpectBucketCount(
+      "Ads.InterestGroup.Auction.LoadGroupsCacheHit", true, 2);
+
+  caching_storage->RecordInterestGroupWin(
+      blink::InterestGroupKey(owner, "name"), "");
+  loaded_igs = GetInterestGroupsForOwner(caching_storage.get(), owner);
+  histogram_tester.ExpectBucketCount(
+      "Ads.InterestGroup.Auction.LoadGroupsCacheHit", false, 3);
+
+  loaded_igs = GetInterestGroupsForOwner(caching_storage.get(), owner);
+  histogram_tester.ExpectBucketCount(
+      "Ads.InterestGroup.Auction.LoadGroupsCacheHit", true, 3);
+}
+
 }  // namespace content
diff --git a/content/browser/media/media_keys_listener_manager_impl.h b/content/browser/media/media_keys_listener_manager_impl.h
index e60b58b7..f9a803d7 100644
--- a/content/browser/media/media_keys_listener_manager_impl.h
+++ b/content/browser/media/media_keys_listener_manager_impl.h
@@ -147,6 +147,8 @@
   bool auxiliary_services_started_ = false;
 
   bool is_media_playing_ = false;
+
+  friend class WebAppSystemMediaControlsBrowserTest;
 };
 
 }  // namespace content
diff --git a/content/browser/media/web_app_system_media_controls_browsertest.cc b/content/browser/media/web_app_system_media_controls_browsertest.cc
new file mode 100644
index 0000000..98026b5
--- /dev/null
+++ b/content/browser/media/web_app_system_media_controls_browsertest.cc
@@ -0,0 +1,195 @@
+// 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/run_loop.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/threading/platform_thread_win.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/media/media_keys_listener_manager_impl.h"
+#include "content/browser/media/web_app_system_media_controls.h"
+#include "content/browser/media/web_app_system_media_controls_manager.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/media_start_stop_observer.h"
+#include "content/shell/browser/shell.h"
+#include "media/base/media_switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+// This test suite tests playing media in a content window and verifies control
+// via system media controls controls the expected window.
+// As instanced system media controls is developed under
+// kWebAppSystemMediaControlsWin this suite will expand to focus on testing
+// instanced web app system media controls.
+
+// Currently, this test suite only runs on windows.
+class WebAppSystemMediaControlsBrowserTest
+    : public ContentBrowserTest,
+      public system_media_controls::SystemMediaControlsObserver {
+ public:
+  WebAppSystemMediaControlsBrowserTest() = default;
+
+  WebAppSystemMediaControlsBrowserTest(
+      const WebAppSystemMediaControlsBrowserTest&) = delete;
+  WebAppSystemMediaControlsBrowserTest& operator=(
+      const WebAppSystemMediaControlsBrowserTest&) = delete;
+
+  ~WebAppSystemMediaControlsBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    // Start an HTTPS server that will serve files in from "content/test/data".
+    https_server_ = std::make_unique<net::EmbeddedTestServer>(
+        net::EmbeddedTestServer::TYPE_HTTPS);
+    https_server_->ServeFilesFromSourceDirectory("content/test/data");
+    ASSERT_TRUE(https_server_->Start());
+  }
+
+  net::EmbeddedTestServer* https_server() { return https_server_.get(); }
+
+  // SystemMediaControlsObserver
+  void OnServiceReady() override {
+    if (waiting_for_service_ready_) {
+      ASSERT_NE(service_ready_run_loop_, nullptr);
+      waiting_for_service_ready_ = false;
+      service_ready_run_loop_->Quit();
+    }
+  }
+
+  // After media plays, there can be an arbitrary delay before system media
+  // controls is ready to receive control requests. This function allows a
+  // mechanism to wait until the system media controls fires OnServiceReady
+  // before attempting to continue with the test.
+  void PrepareWaitForOnServiceReady(base::RunLoop* run_loop) {
+    waiting_for_service_ready_ = true;
+    service_ready_run_loop_ = run_loop;
+  }
+
+  void StartPlaybackAndWait(Shell* shell, const std::string& id) {
+    shell->web_contents()->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
+        base::ASCIIToUTF16(
+            JsReplace("document.getElementById($1).play();", id)),
+        base::NullCallback());
+    WaitForStart(shell);
+  }
+
+  void WaitForStart(Shell* shell) {
+    MediaStartStopObserver observer(shell->web_contents(),
+                                    MediaStartStopObserver::Type::kStart);
+    observer.Wait();
+  }
+
+  void WaitForStop(Shell* shell) {
+    MediaStartStopObserver observer(shell->web_contents(),
+                                    MediaStartStopObserver::Type::kStop);
+    observer.Wait();
+  }
+
+  bool IsPlaying(Shell* shell, const std::string& id) {
+    return EvalJs(shell->web_contents(),
+                  JsReplace("!document.getElementById($1).paused;", id))
+        .ExtractBool();
+  }
+
+  system_media_controls::SystemMediaControls* GetBrowserSystemMediaControls() {
+    MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
+        BrowserMainLoop::GetInstance()->media_keys_listener_manager();
+    return media_keys_listener_manager_impl->system_media_controls_.get();
+  }
+
+ protected:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitchASCII(
+        switches::kAutoplayPolicy,
+        switches::autoplay::kNoUserGestureRequiredPolicy);
+
+    feature_list_.InitAndEnableFeature(features::kWebAppSystemMediaControlsWin);
+    ContentBrowserTest::SetUpCommandLine(command_line);
+  }
+
+ private:
+  bool waiting_for_service_ready_ = false;
+  raw_ptr<base::RunLoop> service_ready_run_loop_ = nullptr;
+  std::unique_ptr<net::EmbeddedTestServer> https_server_;
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(WebAppSystemMediaControlsBrowserTest,
+                       SimpleOneBrowserTest) {
+  GURL http_url(https_server()->GetURL("/media/session/media-session.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), http_url));
+
+  base::RunLoop run_loop;
+  PrepareWaitForOnServiceReady(&run_loop);
+
+  // Run javascript to play the video, and wait for it to begin playing.
+  StartPlaybackAndWait(shell(), "long-video-loop");
+  // Check video is playing.
+  EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
+
+  // Occasionally, the browser system media controls can not be ready.
+  // If that happens, just retry a few more times.
+  int max_retries = 3;
+  for (int retry = 0; retry < max_retries; retry++) {
+    if (GetBrowserSystemMediaControls() != nullptr) {
+      break;
+    }
+
+    // Check the video continues to play.
+    EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
+  }
+
+  EXPECT_NE(GetBrowserSystemMediaControls(), nullptr);
+
+  GetBrowserSystemMediaControls()->AddObserver(this);
+  // Wait till the System Media Controls are ready to be used.
+  run_loop.Run();
+
+  // Hit pause via simulating SMTC pause.
+  MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
+      BrowserMainLoop::GetInstance()->media_keys_listener_manager();
+  media_keys_listener_manager_impl->OnPause(GetBrowserSystemMediaControls());
+
+  // Check video is paused.
+  WaitForStop(shell());
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppSystemMediaControlsBrowserTest, ThreeBrowserTest) {
+  GURL http_url(https_server()->GetURL("/media/session/media-session.html"));
+
+  Shell* browser2 = CreateBrowser();
+  Shell* browser3 = CreateBrowser();
+
+  EXPECT_TRUE(NavigateToURL(shell(), http_url));
+  EXPECT_TRUE(NavigateToURL(browser2, http_url));
+  EXPECT_TRUE(NavigateToURL(browser3, http_url));
+
+  // Press play and wait for each one to start.
+  StartPlaybackAndWait(shell(), "long-video-loop");
+  StartPlaybackAndWait(browser2, "long-video-loop");
+  StartPlaybackAndWait(browser3, "long-video-loop");
+
+  EXPECT_TRUE(IsPlaying(browser3, "long-video-loop"));
+  EXPECT_TRUE(IsPlaying(browser2, "long-video-loop"));
+  EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
+
+  // Now we have 3 things playing at the same time.
+  // Browser 3 should have control and be shown in SMTC.
+
+  // Hit pause via simulating SMTC pause.
+  MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
+      BrowserMainLoop::GetInstance()->media_keys_listener_manager();
+  media_keys_listener_manager_impl->OnPause(GetBrowserSystemMediaControls());
+
+  // Check audio is paused for browser3.
+  WaitForStop(browser3);
+
+  // The other stuff should be continuing to loop.
+  EXPECT_TRUE(IsPlaying(browser2, "long-video-loop"));
+  EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
+}
+
+}  // namespace content
diff --git a/content/browser/network_service_instance_impl.cc b/content/browser/network_service_instance_impl.cc
index 24b9300..b47cc15 100644
--- a/content/browser/network_service_instance_impl.cc
+++ b/content/browser/network_service_instance_impl.cc
@@ -444,6 +444,8 @@
   }
 #endif  // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
 
+  network_service_params->ip_protection_proxy_bypass_policy =
+      GetContentClient()->browser()->GetIpProtectionProxyBypassPolicy();
   return network_service_params;
 }
 
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index c1c8470..b95724c 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -3118,6 +3118,9 @@
   // for the redirected one.
   commit_params_->not_restored_reasons = nullptr;
 
+  // Reset the tentative origin_to_commit, as the redirected one is different.
+  tentative_data_origin_to_commit_ = absl::nullopt;
+
   // A request was made. Record it before we decide to block this response for
   // a reason or another.
   RecordAddressSpaceFeature();
@@ -3872,6 +3875,24 @@
     DCHECK(!url_info_init.origin().has_value());
   }
 
+  // Propagate the tentative origin to commit value (for data: URLs that will be
+  // rendered) to the UrlInfo, to make sure the nonce remains the same
+  // throughout the navigation.
+  if (GetURL().SchemeIs(url::kDataScheme) &&
+      base::FeatureList::IsEnabled(features::kDataUrlsHaveStableNonce)) {
+    // The function for computing the request's origin depends on the stage of
+    // the request, but the same opaque nonce value is preserved across both
+    // functions for data: URLs.
+    if (state_ < WILL_PROCESS_RESPONSE) {
+      url_info_init.WithOrigin(GetTentativeOriginAtRequestTime());
+    } else if (response_should_be_rendered_) {
+      // The origin to commit is nullopt for cases that are not rendered (e.g.,
+      // downloads), but the UrlInfo does not need the origin for data: URLs in
+      // such cases.
+      url_info_init.WithOrigin(GetOriginToCommit().value());
+    }
+  }
+
   // Determine if the request is for a sandboxed frame or not, and if so whether
   // the sandboxed frame should get a dedicated process. Setting
   // `has_origin_restricted_sandbox_flag` to true indicates it should get
@@ -9906,6 +9927,17 @@
         "load_data_with_base_url");
   }
 
+  // Use the cached tentative data origin to commit in the data: URL case. When
+  // there are multiple data: URLs in the same SiteInstanceGroup, we can rely on
+  // the nonce from the origin and that of the site URL to match, which will let
+  // us uniquely identify the correct data: SiteInstance.
+  if (common_params().url.SchemeIs(url::kDataScheme) &&
+      tentative_data_origin_to_commit_.has_value() &&
+      base::FeatureList::IsEnabled(features::kDataUrlsHaveStableNonce)) {
+    return std::make_pair(tentative_data_origin_to_commit_.value(),
+                          "data: URL");
+  }
+
   // Srcdoc subframes need to inherit their origin from their parent frame.
   if (GetURL().IsAboutSrcdoc()) {
     RenderFrameHostImpl* parent = frame_tree_node()->parent();
@@ -9922,11 +9954,17 @@
 
   // In cases not covered above, URLLoaderFactory should be associated with the
   // origin of |common_params.url| and/or |common_params.initiator_origin|.
-  return std::make_pair(
-      url::Origin::Resolve(
-          common_params().url,
-          common_params().initiator_origin.value_or(url::Origin())),
-      "url_or_initiator");
+  url::Origin resolved_origin = url::Origin::Resolve(
+      common_params().url,
+      common_params().initiator_origin.value_or(url::Origin()));
+
+  if (common_params().url.SchemeIs(url::kDataScheme) &&
+      base::FeatureList::IsEnabled(features::kDataUrlsHaveStableNonce)) {
+    // Cache the origin for data: URLs, so that its nonce remains stable.
+    tentative_data_origin_to_commit_ = resolved_origin;
+  }
+
+  return std::make_pair(resolved_origin, "url_or_initiator");
 }
 
 url::Origin NavigationRequest::GetOriginForURLLoaderFactoryUnchecked() {
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 7db9e2a..595133d4 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -2791,6 +2791,15 @@
   // advertising porpoises.
   bool is_ad_tagged_ = false;
 
+  // This is the origin to commit value calculated at request time for data: URL
+  // navigations. It is stored so that the opaque origin nonce can be maintained
+  // across a navigation. This value is used when a speculative SiteInstance
+  // is created so the site URL of the navigation can match the initiator
+  // origin. We store the tentative origin to commit value, since we need it
+  // before ready to commit time, which is when the regular origin to commit
+  // value is available.
+  absl::optional<url::Origin> tentative_data_origin_to_commit_;
+
   base::WeakPtrFactory<NavigationRequest> weak_factory_{this};
 };
 
diff --git a/content/browser/renderer_host/navigation_request_browsertest.cc b/content/browser/renderer_host/navigation_request_browsertest.cc
index 451256d..02f33034 100644
--- a/content/browser/renderer_host/navigation_request_browsertest.cc
+++ b/content/browser/renderer_host/navigation_request_browsertest.cc
@@ -532,6 +532,11 @@
 }  // namespace
 
 class NavigationRequestBrowserTest : public ContentBrowserTest {
+ public:
+  WebContentsImpl* contents() const {
+    return static_cast<WebContentsImpl*>(shell()->web_contents());
+  }
+
  protected:
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
@@ -4964,4 +4969,35 @@
   }
 }
 
+// data: URLs should have opaque origins with nonce that is stable across a
+// navigation, which is stored as the tentative origin to commit.
+IN_PROC_BROWSER_TEST_F(NavigationRequestBrowserTest,
+                       TentativeOriginToCommitIsStable_Data) {
+  // Start a navigation to a data URL with an opaque origin.
+  const std::string data = "<html><title>One</title><body>foo</body></html>";
+  const GURL data_url = GURL("data:text/html;charset=utf-8," + data);
+  TestNavigationManager navigation_manager(contents(), data_url);
+  shell()->LoadURL(data_url);
+  EXPECT_TRUE(navigation_manager.WaitForRequestStart());
+
+  // Get a copy of the computed origin at an early stage, both from the
+  // NavigationRequest and the UrlInfo, where the values are set and used.
+  NavigationRequest* data_request =
+      contents()->GetPrimaryFrameTree().root()->navigation_request();
+  absl::optional<url::Origin> data_tentative_origin_to_commit =
+      data_request->GetTentativeOriginAtRequestTime();
+  EXPECT_TRUE(data_tentative_origin_to_commit.has_value());
+  absl::optional<url::Origin> url_info_origin =
+      data_request->GetUrlInfo().origin;
+  EXPECT_TRUE(url_info_origin.has_value());
+  EXPECT_EQ(data_tentative_origin_to_commit, url_info_origin);
+
+  // Verify that the committed origin at the end matches the initial one.
+  EXPECT_TRUE(navigation_manager.WaitForNavigationFinished());
+  url::Origin data_committed_origin =
+      contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin();
+  EXPECT_EQ(data_tentative_origin_to_commit.value(), data_committed_origin);
+  EXPECT_EQ(url_info_origin.value(), data_committed_origin);
+}
+
 }  // namespace content
diff --git a/content/browser/site_info.cc b/content/browser/site_info.cc
index 20056e6..b4084fd 100644
--- a/content/browser/site_info.cc
+++ b/content/browser/site_info.cc
@@ -15,6 +15,7 @@
 #include "content/browser/renderer_host/render_process_host_impl.h"
 #include "content/browser/site_instance_impl.h"
 #include "content/browser/webui/url_data_manager_backend.h"
+#include "content/common/features.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/site_isolation_policy.h"
 #include "content/public/browser/storage_partition_config.h"
@@ -77,12 +78,16 @@
 
 // Figure out which origin to use for computing site and process lock URLs for
 // `url`. In most cases, this should just be `url`'s origin. However, there are
-// some exceptions where an alternate origin must be used. Namely, for
-// LoadDataWithBaseURL navigations, this should be the origin of the base URL
-// rather than the data URL. about:blank navigations ought to inherit the
-// initiator's origin. In all these cases, we should use the alternate origin
-// which will be passed through `overridden_origin`, ensuring to use its
-// precursor if the origin is opaque to still compute a meaningful site URL.
+// some exceptions where an alternate origin must be used.
+//   - data: URLs: The tentative origin to commit, stored in `overridden_origin`
+//     should be used. We store the value there because it's an opaque origin
+//     and this lets us use the same nonce throughout the navigation.
+//   - LoadDataWithBaseURL: The origin of the base URL should be used, rather
+//     than the data URL.
+//   - about:blank: The initiator's origin should be inherited.
+// In all these cases, we should use the alternate origin which will be passed
+// through `overridden_origin`, ensuring to use its precursor in the about:blank
+// case if the origin is opaque to still compute a meaningful site URL.
 url::Origin GetPossiblyOverriddenOriginFromUrl(
     const GURL& url,
     absl::optional<url::Origin> overridden_origin) {
@@ -90,7 +95,20 @@
       url.SchemeIs(url::kDataScheme) || url.IsAboutBlank();
   if (overridden_origin.has_value() && scheme_allows_origin_override) {
     auto precursor = overridden_origin->GetTupleOrPrecursorTupleIfOpaque();
-    if (precursor.IsValid()) {
+    if (url.SchemeIs(url::kDataScheme) &&
+        base::FeatureList::IsEnabled(features::kDataUrlsHaveStableNonce)) {
+      // data: URLs have an overridden origin so they can have the same nonce
+      // over the course of a navigation.
+      // This is checked first, since we don't want to use the precursor for
+      // data: URLs. For regular data: URLs, we should use the overridden_origin
+      // value, not the precursor. In the LoadDataWithBaseURL case, the base URL
+      // which is a real, non-opaque origin is used, and should also not use the
+      // precursor. We don't expect LoadDataWithBaseURL to have an opaque origin
+      // with a precursor in any case. If there is no base URL, then it should
+      // be treated as a regular data: URL.
+      return overridden_origin.value();
+    } else if (precursor.IsValid()) {
+      // The precursor should only be used in the about:blank case.
       return url::Origin::CreateFromNormalizedTuple(
           precursor.scheme(), precursor.host(), precursor.port());
     } else {
diff --git a/content/browser/sms/sms_fetcher_impl.cc b/content/browser/sms/sms_fetcher_impl.cc
index c76d453d..0036974 100644
--- a/content/browser/sms/sms_fetcher_impl.cc
+++ b/content/browser/sms/sms_fetcher_impl.cc
@@ -96,8 +96,10 @@
   auto it = remote_cancel_callbacks_.find(subscriber);
   if (it == remote_cancel_callbacks_.end())
     return;
-  std::move(it->second).Run();
+  auto cancel_callback = std::move(it->second);
   remote_cancel_callbacks_.erase(it);
+
+  std::move(cancel_callback).Run();
 }
 
 bool SmsFetcherImpl::Notify(const OriginList& origin_list,
diff --git a/content/browser/url_info.h b/content/browser/url_info.h
index 90bd76e7..1576b5e 100644
--- a/content/browser/url_info.h
+++ b/content/browser/url_info.h
@@ -139,11 +139,17 @@
   bool is_coop_isolation_requested = false;
 
   // This allows overriding the origin of |url| for process assignment purposes
-  // in certain very special cases. If the navigation to |url| is performed via
-  // the loadDataWithBaseURL API (e.g., in a <webview> tag or on Android
-  // Webview), this will be the base origin provided via that API. For renderer-
-  // initiated about:blank navigations, this will be the initiator's origin that
-  // about:blank should inherit. Otherwise, this will be nullopt.
+  // in certain very special cases.
+  // - The navigation to |url| is through loadDataWithBaseURL (e.g., in a
+  //   <webview> tag or on Android Webview): this will be the base origin
+  //   provided via that API.
+  // - For renderer-initiated about:blank navigations: this will be the
+  //   initiator's origin that about:blank should inherit.
+  // - data: URLs that will be rendered (e.g. not downloads) that do NOT use
+  //   loadDataWithBaseURL: this will be the value of the tentative origin to
+  //   commit, which we will use to keep the nonce of the opaque origin
+  //   consistent across a navigation.
+  // - All other cases: this will be nullopt.
   //
   // TODO(alexmos): Currently, this is also used to hold the origin committed
   // by the renderer at DidCommitNavigation() time, for use in commit-time URL
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index c590a81a..f5264579 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -388,8 +388,6 @@
      kSetOnlyIfOverridden},
     {"TopicsAPI", raw_ref(features::kPrivacySandboxAdsAPIsM1Override),
      kSetOnlyIfOverridden},
-    {"TopicsXHR", raw_ref(features::kPrivacySandboxAdsAPIsOverride),
-     kSetOnlyIfOverridden},
     {"TopicsDocumentAPI", raw_ref(features::kPrivacySandboxAdsAPIsOverride),
      kSetOnlyIfOverridden},
     {"TopicsDocumentAPI", raw_ref(features::kPrivacySandboxAdsAPIsM1Override),
@@ -615,24 +613,16 @@
   }
 
   // Topics API cannot be enabled without the support of the browser process.
-  // The XHR attribute should be additionally gated by the `kBrowsingTopicsXHR`
-  // feature and the Document API by the `kBrowsingTopicsDocumentAPI` feature.
+  // The Document API should be additionally gated by the
+  // `kBrowsingTopicsDocumentAPI` feature.
   if (!base::FeatureList::IsEnabled(blink::features::kBrowsingTopics)) {
     LOG_IF(WARNING, WebRuntimeFeatures::IsTopicsAPIEnabled())
         << "Topics cannot be enabled in this configuration. Use --"
         << switches::kEnableFeatures << "="
         << blink::features::kBrowsingTopics.name << " in addition.";
     WebRuntimeFeatures::EnableTopicsAPI(false);
-    WebRuntimeFeatures::EnableTopicsXHR(false);
     WebRuntimeFeatures::EnableTopicsDocumentAPI(false);
   } else {
-    if (!base::FeatureList::IsEnabled(blink::features::kBrowsingTopicsXHR)) {
-      LOG_IF(WARNING, WebRuntimeFeatures::IsTopicsXHREnabled())
-          << "Topics XHR cannot be enabled in this configuration. Use --"
-          << switches::kEnableFeatures << "="
-          << blink::features::kBrowsingTopicsXHR.name << " in addition.";
-      WebRuntimeFeatures::EnableTopicsXHR(false);
-    }
     if (!base::FeatureList::IsEnabled(
             blink::features::kBrowsingTopicsDocumentAPI)) {
       LOG_IF(WARNING, WebRuntimeFeatures::IsTopicsDocumentAPIEnabled())
diff --git a/content/common/features.cc b/content/common/features.cc
index 41cd94a..585e790 100644
--- a/content/common/features.cc
+++ b/content/common/features.cc
@@ -124,6 +124,14 @@
              "CriticalClientHint",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Enables setting the nonce of the data: opaque origin early in the navigation
+// so the nonce remains stable throughout a navigation.
+// TODO(crbug.com/1447896, yangsharon): Remove this once we're confident that
+// this change isn't causing issues in the wild.
+BASE_FEATURE(kDataUrlsHaveStableNonce,
+             "DataUrlsHaveStableNonce",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Enable changing source dynamically for desktop capture.
 BASE_FEATURE(kDesktopCaptureChangeSource,
              "DesktopCaptureChangeSource",
diff --git a/content/common/features.h b/content/common/features.h
index 99cb00c..4a3a683 100644
--- a/content/common/features.h
+++ b/content/common/features.h
@@ -30,6 +30,7 @@
 BASE_DECLARE_FEATURE(kConsolidatedIPCForProxyCreation);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kConsolidatedMovementXY);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kCriticalClientHint);
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kDataUrlsHaveStableNonce);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kDesktopCaptureChangeSource);
 #if BUILDFLAG(IS_MAC)
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kDeviceMonitorMac);
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeTest.java b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeTest.java
index 9e0665b..581462c 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeTest.java
@@ -103,24 +103,6 @@
         mRule.assertWaitForKeyboardStatus(false);
     }
 
-    @Test
-    @MediumTest
-    @Feature({"TextInput", "Main"})
-    public void testDoesNotHang_getTextAfterKeyboardHides() throws Throwable {
-        mRule.setComposingText("hello", 1);
-        mRule.waitAndVerifyUpdateSelection(0, 5, 5, 0, 5);
-
-        mRule.performGo(mRule.getTestCallBackHelperContainer());
-
-        // This may time out if we do not get the information on time.
-        // TODO(changwan): find a way to remove the loop.
-        for (int i = 0; i < 100; ++i) {
-            mRule.getTextBeforeCursor(10, 0);
-        }
-
-        mRule.assertWaitForKeyboardStatus(false);
-    }
-
     // crbug.com/643519
     @Test
     @SmallTest
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 3f40f21..a5d55e0 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -1658,4 +1658,8 @@
     BrowserContext* browser_context,
     blink::WebMediaDeviceInfoArray& infos) {}
 
+network::mojom::IpProtectionProxyBypassPolicy
+ContentBrowserClient::GetIpProtectionProxyBypassPolicy() {
+  return network::mojom::IpProtectionProxyBypassPolicy::kNone;
+}
 }  // namespace content
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 29b2519..429ddad 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -56,6 +56,7 @@
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "services/network/public/mojom/ip_address_space.mojom-forward.h"
 #include "services/network/public/mojom/network_context.mojom-forward.h"
+#include "services/network/public/mojom/proxy_config.mojom-forward.h"
 #include "services/network/public/mojom/restricted_cookie_manager.mojom-forward.h"
 #include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
 #include "services/network/public/mojom/web_sandbox_flags.mojom-forward.h"
@@ -2753,6 +2754,15 @@
   virtual void PreferenceRankAudioDeviceInfos(
       BrowserContext* browser_context,
       blink::WebMediaDeviceInfoArray& infos);
+
+  // Allows the embedder to override the proxy bypass policy used for IP
+  // Protection.
+  // Even if a domain is part of the masked domain list and is
+  // eligible for IP Protection, the embedder can use a certain policy to bypass
+  // certain network requests from IP Protection.
+  // By default, there is no bypass policy used.
+  virtual network::mojom::IpProtectionProxyBypassPolicy
+  GetIpProtectionProxyBypassPolicy();
 };
 
 }  // namespace content
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 040cf8c..487f58f 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -1134,6 +1134,11 @@
 // Enables future V8 VM features
 BASE_FEATURE(kV8VmFuture, "V8VmFuture", base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables per PWA System Media Controls on Windows
+BASE_FEATURE(kWebAppSystemMediaControlsWin,
+             "WebAppSystemMediaControlsWin",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enable WebAssembly baseline compilation (Liftoff).
 BASE_FEATURE(kWebAssemblyBaseline,
              "WebAssemblyBaseline",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 7e1358a5..a74ab36 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -283,6 +283,7 @@
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kVerifyDidCommitParams);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kViewportSegments);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kV8VmFuture);
+CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAppSystemMediaControlsWin);
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kWebAssemblyBaseline);
 #if defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64)
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kEnableExperimentalWebAssemblyJSPI);
diff --git a/content/renderer/java/gin_java_function_invocation_helper.cc b/content/renderer/java/gin_java_function_invocation_helper.cc
index 6175e38d..85e8cc4 100644
--- a/content/renderer/java/gin_java_function_invocation_helper.cc
+++ b/content/renderer/java/gin_java_function_invocation_helper.cc
@@ -73,15 +73,19 @@
     }
   }
 
-  mojom::GinJavaBridgeError error;
+  mojom::GinJavaBridgeError error =
+      mojom::GinJavaBridgeError::kGinJavaBridgeNoError;
 
   std::unique_ptr<base::Value> result;
   if (auto* remote = object->GetRemote()) {
     base::Value::List result_wrapper;
-    remote->InvokeMethod(method_name_, std::move(arguments), &error,
-                         &result_wrapper);
-    if (!result_wrapper.empty()) {
-      result = base::Value::ToUniquePtrValue(result_wrapper[0].Clone());
+    if (remote->InvokeMethod(method_name_, std::move(arguments), &error,
+                             &result_wrapper)) {
+      if (!result_wrapper.empty()) {
+        result = base::Value::ToUniquePtrValue(result_wrapper[0].Clone());
+      }
+    } else {
+      error = mojom::GinJavaBridgeError::kGinJavaBridgeObjectIsGone;
     }
   } else {
     result = dispatcher_->InvokeJavaMethod(object->object_id(), method_name_,
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index cfaed92..044b4fe3 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1945,6 +1945,7 @@
       "../browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc",
       "../browser/accessibility/ax_platform_node_win_browsertest.cc",
       "../browser/accessibility/hit_testing_win_browsertest.cc",
+      "../browser/media/web_app_system_media_controls_browsertest.cc",
       "../browser/network/network_service_process_tracker_win_browsertest.cc",
       "../browser/renderer_host/accessibility_object_lifetime_win_browsertest.cc",
       "../browser/renderer_host/accessibility_tree_linkage_win_browsertest.cc",
diff --git a/content/test/data/media/session/media-session.html b/content/test/data/media/session/media-session.html
index 46a7deae..6d5b697 100644
--- a/content/test/data/media/session/media-session.html
+++ b/content/test/data/media/session/media-session.html
@@ -26,5 +26,8 @@
   <video id='long-video-silent' controls>
     <source src='video-6seconds-silent.webm'>
   </video>
+  <video id='long-video-loop' controls loop>
+    <source src='video-6seconds.webm'>
+  </video>
 </body>
 </html>
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index a69d6f0..a3a1d675 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -725,6 +725,7 @@
 ## Linux NVIDIA ##
 
 crbug.com/1115314 [ linux nvidia-0x2184 angle-opengl passthrough ] deqp/functional/gles3/fbocompleteness.html [ Failure ]
+crbug.com/1502174 [ linux nvidia-0x2184 angle-opengl passthrough ] conformance2/textures/video/tex-2d-rg16f-rg-float.html [ RetryOnFailure ]
 
 ## Linux AMD RX 5500 XT ##
 
diff --git a/docs/mac/arc.md b/docs/mac/arc.md
index aedee5d..a9c788aa 100644
--- a/docs/mac/arc.md
+++ b/docs/mac/arc.md
@@ -20,6 +20,18 @@
 For the rest of this document, the term “Objective-C” will be used to mean both
 pure Objective-C as well as Objective-C++.
 
+### What isn’t handled by ARC {#what-isnt}
+
+Be aware that ARC is only used for Objective-C objects (those objects that are a
+subclass of `NSObject`, declared with `@interface` or `@class`). The ownership
+of Core Foundation objects (with names often starting with `CF`, with names
+often ending with `Ref`, and declared using a `typedef` as a pointer to an
+undefined struct) is not handled by ARC, and `ScopedCFTypeRef<>` must be used to
+manage their lifetimes. For documentation on how the lifetime of Core Foundation
+objects works, and when you will need to use a scoper to manage it, see the
+[Memory Management Programming Guide for Core
+Foundation](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/CFMemoryMgmt.html#//apple_ref/doc/uid/10000127i).
+
 ## The basics of ARC {#basics}
 
 (This is necessarily a simplified explanation; reading ARC
@@ -50,8 +62,8 @@
 - `__weak`: This pointer maintains a weak reference to the object which is kept
   alive by other `__strong` references. If the last of the strong references is
   released, and the object is deallocated, this pointer will be set to `nil`.
-- `__unsafe_unretained`: This is a raw pointer (as in C/C++) which maintains a
-  reference to the object but has no other automatic capabilities.
+- `__unsafe_unretained`: This is a raw pointer (as in C/C++) which has no
+  automatic capabilities.
   - Chromium usage note: Do not use this, as it is almost certainly the wrong
     choice. The `PRESUBMIT` will complain.
 
diff --git a/docs/security/clusterfuzz-for-shepherds.md b/docs/security/clusterfuzz-for-shepherds.md
index 3454444..9f96c07 100644
--- a/docs/security/clusterfuzz-for-shepherds.md
+++ b/docs/security/clusterfuzz-for-shepherds.md
@@ -74,3 +74,15 @@
 ClusterFuzz [code is in
 gesture_handler.py](https://github.com/google/clusterfuzz/blob/master/src/clusterfuzz/_internal/fuzzing/gesture_handler.py#L22)
 to figure out the languages for other platforms.
+
+## HTTP(S) headers
+
+If you need to reproduce a test case that involves specific HTTP headers, do this:
+
+1. Make a copy of [page_load_in_process_fuzzer_seed_corpus/network.textproto](https://source.chromium.org/chromium/chromium/src/+/main:chrome/test/fuzzing/page_load_in_process_fuzzer_seed_corpus/network.textproto)
+2. Edit as necessary to give the headers you need
+3. Assuming an output directory of `out/Release` and that you've called the file `testcase.textproto`, `python3 tools/protoc_wrapper/protoc_convert.py --protoc out/Release/protoc --infile testcase.textproto --outfile testcase.binarypb --encode=test.fuzzing.page_load_fuzzing.FuzzCase -I . chrome/test/fuzzing/page_load_in_process_fuzzer.proto`
+3. Go to the ClusterFuzz [upload page]((https://clusterfuzz.com/upload-testcase))
+4. Select `libfuzzer_chrome_asan` for the job
+5. Select `page_load_in_process_fuzzer` for the fuzzer
+6. Upload `testcase.binarypb` as the test case.
diff --git a/docs/speed/metrics_changelog/2023_10_image_loading_optimizations.md b/docs/speed/metrics_changelog/2023_10_image_loading_optimizations.md
new file mode 100644
index 0000000..707949b
--- /dev/null
+++ b/docs/speed/metrics_changelog/2023_10_image_loading_optimizations.md
@@ -0,0 +1,29 @@
+# Image loading optimizations in Chrome 118 that impact LCP and CLS
+
+We are launching two complementary changes for image loading optimizations:
+- [Increase the priority of the first 5 images](#increase-the-priority-of-the-first-5-images)
+- [Prioritize image load tasks](#prioritize-image-load-tasks)
+
+## Increase the priority of the first 5 images
+
+Starting around Chrome 118, we are launching the [change](2023_08_image_loading.md)
+to 100% of Chrome sessions.
+
+## Prioritize image load tasks
+
+When we have an image load task and a rendering task to choose from, we choose
+the image load task first to prevent layout shift of an intermediate frame
+that doesn't have the image. For the same reason, LCP improves because we skip
+the intermediate frame.
+
+[Relevant bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1416030)
+
+## How does this affect a site's metrics?
+
+As we are increasing the priority of the first 5 images and image loading tasks,
+sites with images might see better CLS and LCP scores.
+
+## When were users affected?
+
+Both changes were rolled out to 100% of Chrome users around October 1, 2023 which
+roughly corresponds with the release of Chrome 118.
\ No newline at end of file
diff --git a/docs/speed/metrics_changelog/2023_10_lcp.md b/docs/speed/metrics_changelog/2023_10_lcp.md
index 2c9c9d8..98a9a90 100644
--- a/docs/speed/metrics_changelog/2023_10_lcp.md
+++ b/docs/speed/metrics_changelog/2023_10_lcp.md
@@ -1,4 +1,4 @@
-#Largest Contentful Paint bug in Chrome 117
+# Largest Contentful Paint bug in Chrome 117
 
 Chromium 117 introduced a bug in LCP, where web applications that trigger 
 [soft navigation heuristics](https://github.com/WICG/soft-navigations) may get
diff --git a/docs/speed/metrics_changelog/cls.md b/docs/speed/metrics_changelog/cls.md
index 5be23af..0c041fa 100644
--- a/docs/speed/metrics_changelog/cls.md
+++ b/docs/speed/metrics_changelog/cls.md
@@ -2,6 +2,8 @@
 
 This is a list of changes to [Cumulative Layout Shift](https://web.dev/cls).
 
+* Chrome 118
+  * Implementation optimizations: [Image loading prioritizations](2023_10_image_loading_optimizations.md)
 * Chrome 116
   * Implementation optimizations: [Optimizing image load scheduling](2023_08_image_loading.md)
 * Chrome 98
diff --git a/docs/speed/metrics_changelog/lcp.md b/docs/speed/metrics_changelog/lcp.md
index e59d311..4271f38 100644
--- a/docs/speed/metrics_changelog/lcp.md
+++ b/docs/speed/metrics_changelog/lcp.md
@@ -2,6 +2,8 @@
 
 This is a list of changes to [Largest Contentful Paint](https://web.dev/lcp).
 
+* Chrome 118
+  * Implementation optimizations: [Image loading prioritizations](2023_10_image_loading_optimizations.md)
 * Chrome 117
   * Bug: [Spurious LCP entries after user interaction](2023_10_lcp.md)
 * Chrome 116
diff --git a/extensions/browser/script_injection_tracker.cc b/extensions/browser/script_injection_tracker.cc
index ad241ddd..2059a59 100644
--- a/extensions/browser/script_injection_tracker.cc
+++ b/extensions/browser/script_injection_tracker.cc
@@ -927,11 +927,10 @@
     const mojom::HostID& host_id,
     content::RenderProcessHost& process) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  TRACE_EVENT("extensions",
-              "ScriptInjectionTracker::WillUpdateContentScriptsInRenderer",
-              ChromeTrackEvent::kRenderProcessHost, process,
-              ChromeTrackEvent::kChromeExtensionId,
-              ExtensionIdForTracing(host_id.id));
+  TRACE_EVENT(
+      "extensions", "ScriptInjectionTracker::WillUpdateScriptsInRenderer",
+      ChromeTrackEvent::kRenderProcessHost, process,
+      ChromeTrackEvent::kChromeExtensionId, ExtensionIdForTracing(host_id.id));
 
   scoped_refptr<const Extension> extension =
       FindExtensionByHostId(process.GetBrowserContext(), host_id);
@@ -939,33 +938,44 @@
     return;
   }
 
-  bool any_frame_matches_scripts = false;
-  process.ForEachRenderFrameHost(
-      [&any_frame_matches_scripts,
-       &extension](content::RenderFrameHost* frame) {
-        auto url = frame->GetLastCommittedURL();
+  bool any_frame_matches_content_scripts = false;
+  bool any_frame_matches_user_scripts = false;
+  process.ForEachRenderFrameHost([&any_frame_matches_content_scripts,
+                                  &any_frame_matches_user_scripts,
+                                  &extension](content::RenderFrameHost* frame) {
+    auto url = frame->GetLastCommittedURL();
 
-        if (extension->id() == kExtensionAffectedByBug1439642) {
-          const ExtensionRegistry* registry =
-              ExtensionRegistry::Get(frame->GetProcess()->GetBrowserContext());
-          DCHECK(registry);  // This method shouldn't be called during shutdown.
-          AddFrameDebugStringForBug1439642(*registry, "WUSIR", *frame, url);
-        }
+    if (extension->id() == kExtensionAffectedByBug1439642) {
+      const ExtensionRegistry* registry =
+          ExtensionRegistry::Get(frame->GetProcess()->GetBrowserContext());
+      DCHECK(registry);  // This method shouldn't be called during shutdown.
+      AddFrameDebugStringForBug1439642(*registry, "WUSIR", *frame, url);
+    }
 
-        if (DoWebViewScripstMatch(*extension, *frame) ||
-            DoStaticContentScriptsMatch(*extension, *frame, url) ||
-            DoDynamicContentScriptsMatch(*extension, *frame, url) ||
-            DoUserScriptsMatch(*extension, *frame, url)) {
-          any_frame_matches_scripts = true;
-        }
-      });
-  if (any_frame_matches_scripts) {
+    if (!any_frame_matches_content_scripts) {
+      any_frame_matches_content_scripts =
+          DoWebViewScripstMatch(*extension, *frame) ||
+          DoStaticContentScriptsMatch(*extension, *frame, url) ||
+          DoDynamicContentScriptsMatch(*extension, *frame, url);
+    }
+    if (!any_frame_matches_user_scripts) {
+      any_frame_matches_user_scripts =
+          DoUserScriptsMatch(*extension, *frame, url);
+    }
+  });
+
+  if (any_frame_matches_content_scripts || any_frame_matches_user_scripts) {
     auto& process_data = RenderProcessHostUserData::GetOrCreate(process);
-    process_data.AddScript(ScriptType::kContentScript, extension->id());
+    if (any_frame_matches_content_scripts) {
+      process_data.AddScript(ScriptType::kContentScript, extension->id());
+    }
+    if (any_frame_matches_user_scripts) {
+      process_data.AddScript(ScriptType::kUserScript, extension->id());
+    }
   } else {
     TRACE_EVENT_INSTANT("extensions",
                         "ScriptInjectionTracker::"
-                        "WillUpdateContentScriptsInRenderer - no matches",
+                        "WillUpdateScriptsInRenderer - no matches",
                         ChromeTrackEvent::kRenderProcessHost, process,
                         ChromeTrackEvent::kChromeExtensionId,
                         ExtensionIdForTracing(host_id.id));
diff --git a/extensions/browser/service_worker/service_worker_keepalive.cc b/extensions/browser/service_worker/service_worker_keepalive.cc
index 8e3a813..21cf5735 100644
--- a/extensions/browser/service_worker/service_worker_keepalive.cc
+++ b/extensions/browser/service_worker/service_worker_keepalive.cc
@@ -72,7 +72,14 @@
 
   ProcessManager* process_manager = ProcessManager::Get(browser_context_);
   CHECK(process_manager);
-  CHECK(process_manager->HasServiceWorker(worker_id_));
+  // TODO(https://crbug.com/1501930): Investigate the circumstances in which
+  // this CHECK can fail. It's possible the service worker called an API before
+  // it fully finished initializing.
+  // This isn't ideal, but the keepalive mechanism in ProcessManager doesn't
+  // rely on a service worker registered in the process manager (all the data
+  // is in the worker ID), so the below is not inherently unsafe (but needs to
+  // be fixed).
+  // CHECK(process_manager->HasServiceWorker(worker_id_));
 
   request_uuid_ = process_manager->IncrementServiceWorkerKeepaliveCount(
       worker_id_, timeout_type, activity_type_, activity_extra_data_);
diff --git a/extensions/common/api/_manifest_features.json b/extensions/common/api/_manifest_features.json
index 52e7c62..f9a5246 100644
--- a/extensions/common/api/_manifest_features.json
+++ b/extensions/common/api/_manifest_features.json
@@ -40,6 +40,11 @@
     "extension_types": ["platform_app"]
   },
   "automation": {
+    // This is a private API which, for historical reasons, does not include
+    // 'private' in the name. It should not be exposed outside of first-party
+    // allow-listed very trusted extensions and component extensions.
+    // This API has no protection in the JS wrappers around untrusted scripts
+    // putting getters/setters to intercept and manipulate native bindings.
     "channel": "stable",
     "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
     "allowlist": [
diff --git a/extensions/common/api/declarative_net_request.idl b/extensions/common/api/declarative_net_request.idl
index 49e031a..01d9e50 100644
--- a/extensions/common/api/declarative_net_request.idl
+++ b/extensions/common/api/declarative_net_request.idl
@@ -781,20 +781,20 @@
 
     // The maximum number of combined dynamic and session scoped rules an
     // extension can add.
-    [value=5000] static long MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES();
+    [nodoc, value=5000] static long MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES();
 
     // The maximum number of dynamic rules that an extension can add.
-    [nodoc, value=30000] static long MAX_NUMBER_OF_DYNAMIC_RULES();
+    [value=30000] static long MAX_NUMBER_OF_DYNAMIC_RULES();
 
     // The maximum number of "unsafe" dynamic rules that an extension can add.
-    [nodoc, value=5000] static long MAX_NUMBER_OF_UNSAFE_DYNAMIC_RULES();
+    [value=5000] static long MAX_NUMBER_OF_UNSAFE_DYNAMIC_RULES();
 
     // The maximum number of session scoped rules that an extension can add.
-    [nodoc, value=5000] static long MAX_NUMBER_OF_SESSION_RULES();
+    [value=5000] static long MAX_NUMBER_OF_SESSION_RULES();
 
     // The maximum number of "unsafe" session scoped rules that an extension can
     // add.
-    [nodoc, value=5000] static long MAX_NUMBER_OF_UNSAFE_SESSION_RULES();
+    [value=5000] static long MAX_NUMBER_OF_UNSAFE_SESSION_RULES();
 
     // Time interval within which <code>MAX_GETMATCHEDRULES_CALLS_PER_INTERVAL
     // getMatchedRules</code> calls can be made, specified in minutes.
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index 5048ae851..febd5c6 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -11,6 +11,11 @@
 // API Features
 ///////////////////////////////////////////////////////////////////////////////
 
+// Controls the availability of contentSettings.clipboard.
+BASE_FEATURE(kApiContentSettingsClipboard,
+             "ApiContentSettingsClipboard",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Controls the availability of the enterprise.kioskInput API.
 BASE_FEATURE(kApiEnterpriseKioskInput,
              "ApiEnterpriseKioskInput",
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index eebefcdd..24aca7da9 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -34,6 +34,7 @@
 // NOTE(devlin): If there are consistently enough of these in flux, it might
 // make sense to have their own file.
 
+BASE_DECLARE_FEATURE(kApiContentSettingsClipboard);
 BASE_DECLARE_FEATURE(kApiEnterpriseKioskInput);
 BASE_DECLARE_FEATURE(kApiReadingList);
 BASE_DECLARE_FEATURE(kApiRuntimeGetContexts);
diff --git a/extensions/common/features/feature_flags.cc b/extensions/common/features/feature_flags.cc
index c863ee7..83cf2ed 100644
--- a/extensions/common/features/feature_flags.cc
+++ b/extensions/common/features/feature_flags.cc
@@ -20,6 +20,7 @@
 // kill switches for extension features. Note any such feature flags must
 // generally be removed once the API has been stable for a few releases.
 const base::Feature* kFeatureFlags[] = {
+    &extensions_features::kApiContentSettingsClipboard,
     &extensions_features::kApiEnterpriseKioskInput,
     &extensions_features::kApiReadingList,
     &extensions_features::kApiRuntimeGetContexts,
diff --git a/gin/converter.h b/gin/converter.h
index 67b8d90..2afca004 100644
--- a/gin/converter.h
+++ b/gin/converter.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include <concepts>
 #include <ostream>
 #include <string>
 #include <type_traits>
@@ -37,14 +38,20 @@
   return !maybe.IsNothing() && maybe.FromJust();
 }
 
-template <typename T, typename Enable = void>
-struct ToV8ReturnsMaybe {
-  static const bool value = false;
-};
-
 template<typename T, typename Enable = void>
 struct Converter {};
 
+namespace internal {
+
+template <typename T>
+concept ToV8ReturnsMaybe = requires(v8::Isolate* isolate, T value) {
+  {
+    Converter<T>::ToV8(isolate, value)
+  } -> std::same_as<v8::MaybeLocal<v8::Value>>;
+};
+
+}  // namespace internal
+
 template<>
 struct GIN_EXPORT Converter<bool> {
   static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
@@ -201,7 +208,7 @@
 
 template<typename T>
 struct Converter<std::vector<T> > {
-  static std::conditional_t<ToV8ReturnsMaybe<T>::value,
+  static std::conditional_t<internal::ToV8ReturnsMaybe<T>,
                             v8::MaybeLocal<v8::Value>,
                             v8::Local<v8::Value>>
   ToV8(v8::Isolate* isolate, const std::vector<T>& val) {
@@ -249,7 +256,7 @@
 
 template <typename T>
 struct Converter<v8::LocalVector<T>> {
-  static std::conditional_t<ToV8ReturnsMaybe<v8::Local<T>>::value,
+  static std::conditional_t<internal::ToV8ReturnsMaybe<v8::Local<T>>,
                             v8::MaybeLocal<v8::Value>,
                             v8::Local<v8::Value>>
   ToV8(v8::Isolate* isolate, const v8::LocalVector<T>& val) {
@@ -300,19 +307,9 @@
   }
 };
 
-template<typename T>
-struct ToV8ReturnsMaybe<std::vector<T>> {
-  static const bool value = ToV8ReturnsMaybe<T>::value;
-};
-
-template <typename T>
-struct ToV8ReturnsMaybe<v8::LocalVector<T>> {
-  static const bool value = ToV8ReturnsMaybe<T>::value;
-};
-
 // Convenience functions that deduce T.
 template <typename T>
-std::conditional_t<ToV8ReturnsMaybe<T>::value,
+std::conditional_t<internal::ToV8ReturnsMaybe<T>,
                    v8::MaybeLocal<v8::Value>,
                    v8::Local<v8::Value>>
 ConvertToV8(v8::Isolate* isolate, const T& input) {
@@ -320,20 +317,15 @@
 }
 
 template <typename T>
-std::enable_if_t<ToV8ReturnsMaybe<T>::value, bool> TryConvertToV8(
-    v8::Isolate* isolate,
-    const T& input,
-    v8::Local<v8::Value>* output) {
-  return ConvertToV8(isolate, input).ToLocal(output);
-}
-
-template <typename T>
-std::enable_if_t<!ToV8ReturnsMaybe<T>::value, bool> TryConvertToV8(
-    v8::Isolate* isolate,
-    const T& input,
-    v8::Local<v8::Value>* output) {
-  *output = ConvertToV8(isolate, input);
-  return true;
+bool TryConvertToV8(v8::Isolate* isolate,
+                    const T& input,
+                    v8::Local<v8::Value>* output) {
+  if constexpr (internal::ToV8ReturnsMaybe<T>) {
+    return ConvertToV8(isolate, input).ToLocal(output);
+  } else {
+    *output = ConvertToV8(isolate, input);
+    return true;
+  }
 }
 
 // This crashes when input.size() > v8::String::kMaxLength.
diff --git a/gin/wrappable.h b/gin/wrappable.h
index 60740ce..4e71156 100644
--- a/gin/wrappable.h
+++ b/gin/wrappable.h
@@ -107,14 +107,6 @@
   ~Wrappable() override = default;
 };
 
-template <typename T>
-struct ToV8ReturnsMaybe<
-    T*,
-    typename std::enable_if<
-        std::is_convertible<T*, WrappableBase*>::value>::type> {
-  static const bool value = true;
-};
-
 // This converter handles any subclass of Wrappable.
 template <typename T>
 struct Converter<T*,
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
index 772e9ae..17ecc6e 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
@@ -2438,9 +2438,10 @@
       &passthrough_discardable_manager_, &shared_image_manager_);
 
   surface_ = gl::init::CreateOffscreenGLSurface(display_, gfx::Size(4, 4));
-  context_ = gl::init::CreateGLContext(
-      nullptr, surface_.get(),
-      GenerateGLContextAttribs(context_creation_attribs_, group_.get()));
+  context_ =
+      gl::init::CreateGLContext(nullptr, surface_.get(),
+                                GenerateGLContextAttribsForDecoder(
+                                    context_creation_attribs_, group_.get()));
   context_->MakeCurrent(surface_.get());
 
   command_buffer_service_ = std::make_unique<FakeCommandBufferServiceBase>();
diff --git a/gpu/command_buffer/service/gr_shader_cache.cc b/gpu/command_buffer/service/gr_shader_cache.cc
index 42d71cb..64c64c0 100644
--- a/gpu/command_buffer/service/gr_shader_cache.cc
+++ b/gpu/command_buffer/service/gr_shader_cache.cc
@@ -9,7 +9,6 @@
 #include "base/auto_reset.h"
 #include "base/base64.h"
 #include "base/functional/callback_helpers.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/trace_event/memory_dump_manager.h"
@@ -29,23 +28,6 @@
   return SkData::MakeWithCopy(str.c_str(), str.length());
 }
 
-enum class VkPipelinePopulatedCacheEntryUsage {
-  // These values are persisted to logs. Entries should not be renumbered and
-  // numeric values should never be reused.
-  kUsed = 0,
-  kOverwritten = 1,
-  kDiscardedTooLarge = 2,
-  kDiscardedHaveNewer = 3,
-  kEvicted = 4,
-  kMaxValue = kEvicted
-};
-
-void ReportPipelinePopulatedCacheEntryUsage(
-    VkPipelinePopulatedCacheEntryUsage usage) {
-  UMA_HISTOGRAM_ENUMERATION("GPU.Vulkan.PipelineCache.PopulatedCacheUsage",
-                            usage);
-}
-
 }  // namespace
 
 GrShaderCache::GrShaderCache(size_t max_cache_size_bytes, Client* client)
@@ -74,16 +56,6 @@
   CacheKey cache_key(SkData::MakeWithoutCopy(key.data(), key.size()));
   auto it = store_.Get(cache_key);
 
-  if (IsVkPipelineCacheEntry(cache_key)) {
-    UMA_HISTOGRAM_BOOLEAN("GPU.Vulkan.PipelineCache.LoadCacheHit",
-                          it != store_.end());
-    if (it != store_.end() && it->second.prefetched_but_not_read) {
-      // This entry was loaded from the disk and skia used it.
-      ReportPipelinePopulatedCacheEntryUsage(
-          VkPipelinePopulatedCacheEntryUsage::kUsed);
-    }
-  }
-
   if (it == store_.end())
     return nullptr;
 
@@ -109,11 +81,6 @@
   DCHECK_NE(current_client_id(), kInvalidClientId);
 
   CacheKey cache_key(SkData::MakeWithCopy(key.data(), key.size()));
-  if (IsVkPipelineCacheEntry(cache_key)) {
-    auto size_in_kb = data.size() / 1024;
-    UMA_HISTOGRAM_CUSTOM_COUNTS("GPU.Vulkan.PipelineCache.Size", size_in_kb, 32,
-                                10 * 1024, 100);
-  }
 
   if (data.size() > cache_size_limit_)
     return;
@@ -123,7 +90,7 @@
   if (existing_it != store_.end()) {
     // Skia may ignore the cached entry and regenerate a shader if it fails to
     // link, in which case replace the current version with the latest one.
-    EraseFromCache(existing_it, /*overwriting=*/true);
+    EraseFromCache(existing_it);
   }
 
   CacheData cache_data(SkData::MakeWithCopy(data.data(), data.size()));
@@ -147,10 +114,6 @@
   CacheKey cache_key(MakeData(decoded_key));
 
   if (data.length() > cache_size_limit_) {
-    if (IsVkPipelineCacheEntry(cache_key)) {
-      ReportPipelinePopulatedCacheEntryUsage(
-          VkPipelinePopulatedCacheEntryUsage::kDiscardedTooLarge);
-    }
     return;
   }
 
@@ -160,10 +123,6 @@
   // was loaded off the disk cache. Its better to keep the latest version
   // generated version than overwriting it here.
   if (store_.Get(cache_key) != store_.end()) {
-    if (IsVkPipelineCacheEntry(cache_key)) {
-      ReportPipelinePopulatedCacheEntryUsage(
-          VkPipelinePopulatedCacheEntryUsage::kDiscardedHaveNewer);
-    }
     return;
   }
 
@@ -185,17 +144,10 @@
 }
 
 template <typename Iterator>
-void GrShaderCache::EraseFromCache(Iterator it, bool overwriting) {
+void GrShaderCache::EraseFromCache(Iterator it) {
   lock_.AssertAcquired();
   DCHECK_GE(curr_size_bytes_, it->second.data->size());
 
-  if (it->second.prefetched_but_not_read && IsVkPipelineCacheEntry(it->first)) {
-    // We're about to erase populated entry, it won't be used anymore.
-    ReportPipelinePopulatedCacheEntryUsage(
-        overwriting ? VkPipelinePopulatedCacheEntryUsage::kOverwritten
-                    : VkPipelinePopulatedCacheEntryUsage::kEvicted);
-  }
-
   curr_size_bytes_ -= it->second.data->size();
   store_.Erase(it);
 }
@@ -272,7 +224,7 @@
   DCHECK_LE(size_needed, cache_size_limit_);
 
   while (size_needed + curr_size_bytes_ > cache_size_limit_)
-    EraseFromCache(store_.rbegin(), /*overwriting=*/false);
+    EraseFromCache(store_.rbegin());
 }
 
 void GrShaderCache::StoreVkPipelineCacheIfNeeded(GrDirectContext* gr_context) {
@@ -290,15 +242,6 @@
 
   if (enable_vk_pipeline_cache_ && need_store_pipeline_cache) {
     {
-      base::ScopedClosureRunner uma_runner(base::BindOnce(
-          [](base::Time time) {
-            UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
-                "GPU.Vulkan.PipelineCache.StoreDuration",
-                base::Time::Now() - time, base::Microseconds(1),
-                base::Microseconds(5000), 50);
-          },
-          base::Time::Now()));
-
       gr_context->storeVkPipelineCacheData();
       {
         base::AutoLock auto_lock(lock_);
@@ -308,13 +251,6 @@
   }
 }
 
-// This function is used only to facilitate debug metrics, this is not reliable
-// way and it should be removed when the feature is launched and we know how
-// cache is performing.
-bool GrShaderCache::IsVkPipelineCacheEntry(const CacheKey& key) {
-  return key.data->size() == 4;
-}
-
 int32_t GrShaderCache::current_client_id() const {
   lock_.AssertAcquired();
   auto it = current_client_id_.find(base::PlatformThread::CurrentId());
diff --git a/gpu/command_buffer/service/gr_shader_cache.h b/gpu/command_buffer/service/gr_shader_cache.h
index 3f64bfcf..56b35fd 100644
--- a/gpu/command_buffer/service/gr_shader_cache.h
+++ b/gpu/command_buffer/service/gr_shader_cache.h
@@ -115,7 +115,7 @@
 
   Store::iterator AddToCache(CacheKey key, CacheData data);
   template <typename Iterator>
-  void EraseFromCache(Iterator it, bool overwriting);
+  void EraseFromCache(Iterator it);
 
   void WriteToDisk(const CacheKey& key, CacheData* data);
 
diff --git a/gpu/command_buffer/service/service_utils.cc b/gpu/command_buffer/service/service_utils.cc
index a77e4ef..98dd11a 100644
--- a/gpu/command_buffer/service/service_utils.cc
+++ b/gpu/command_buffer/service/service_utils.cc
@@ -41,19 +41,12 @@
 
 }  // namespace
 
-gl::GLContextAttribs GenerateGLContextAttribs(
+gl::GLContextAttribs GenerateGLContextAttribsForDecoder(
     const ContextCreationAttribs& attribs_helper,
     const ContextGroup* context_group) {
-  return GenerateGLContextAttribs(attribs_helper,
-                                  context_group->use_passthrough_cmd_decoder());
-}
-
-gl::GLContextAttribs GenerateGLContextAttribs(
-    const ContextCreationAttribs& attribs_helper,
-    bool use_passthrough_cmd_decoder) {
   gl::GLContextAttribs attribs;
   attribs.gpu_preference = attribs_helper.gpu_preference;
-  if (use_passthrough_cmd_decoder) {
+  if (context_group->use_passthrough_cmd_decoder()) {
     attribs.bind_generates_resource = attribs_helper.bind_generates_resource;
     attribs.webgl_compatibility_context =
         IsWebGLContextType(attribs_helper.context_type);
@@ -96,6 +89,33 @@
   return attribs;
 }
 
+gl::GLContextAttribs GenerateGLContextAttribsForCompositor(
+    bool use_passthrough_cmd_decoder) {
+  gl::GLContextAttribs attribs;
+  if (use_passthrough_cmd_decoder) {
+    // TODO(vasilyt): switch this to false
+    attribs.bind_generates_resource = true;
+
+    // Always use the global texture and semaphore share group for the
+    // passthrough command decoder
+    attribs.global_texture_share_group = true;
+    attribs.global_semaphore_share_group = true;
+
+    attribs.robust_resource_initialization = true;
+    attribs.robust_buffer_access = true;
+  }
+
+  bool force_es2_context = gl::GetGlWorkarounds().disable_es3gl_context;
+  if (features::UseGles2ForOopR() && use_passthrough_cmd_decoder) {
+    force_es2_context = true;
+  }
+
+  attribs.client_major_es_version = force_es2_context ? 2 : 3;
+  attribs.client_minor_es_version = 0;
+
+  return attribs;
+}
+
 bool UsePassthroughCommandDecoder(const base::CommandLine* command_line) {
   return gl::UsePassthroughCommandDecoder(command_line);
 }
diff --git a/gpu/command_buffer/service/service_utils.h b/gpu/command_buffer/service/service_utils.h
index 9e3742d..c0362f4 100644
--- a/gpu/command_buffer/service/service_utils.h
+++ b/gpu/command_buffer/service/service_utils.h
@@ -17,12 +17,11 @@
 namespace gles2 {
 class ContextGroup;
 
-GPU_GLES2_EXPORT gl::GLContextAttribs GenerateGLContextAttribs(
+GPU_GLES2_EXPORT gl::GLContextAttribs GenerateGLContextAttribsForDecoder(
     const ContextCreationAttribs& attribs_helper,
     const ContextGroup* context_group);
 
-GPU_GLES2_EXPORT gl::GLContextAttribs GenerateGLContextAttribs(
-    const ContextCreationAttribs& attribs_helper,
+GPU_GLES2_EXPORT gl::GLContextAttribs GenerateGLContextAttribsForCompositor(
     bool use_passthrough_cmd_decoder);
 
 // Returns true if the passthrough command decoder has been requested
diff --git a/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.cc b/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.cc
index 77bb6f2..e55acfd 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.cc
@@ -221,6 +221,10 @@
   switch (format.subsampling()) {
     case viz::SharedImageFormat::Subsampling::k420:
       return SkYUVAInfo::Subsampling::k420;
+    case viz::SharedImageFormat::Subsampling::k422:
+      return SkYUVAInfo::Subsampling::k422;
+    case viz::SharedImageFormat::Subsampling::k444:
+      return SkYUVAInfo::Subsampling::k444;
   }
 }
 
diff --git a/gpu/command_buffer/tests/gl_manager.cc b/gpu/command_buffer/tests/gl_manager.cc
index ec97cccaf..b154362 100644
--- a/gpu/command_buffer/tests/gl_manager.cc
+++ b/gpu/command_buffer/tests/gl_manager.cc
@@ -351,17 +351,19 @@
     context_ = scoped_refptr<gl::GLContext>(new gpu::GLContextVirtual(
         share_group_.get(), base_context_->get(), decoder_->AsWeakPtr()));
     ASSERT_TRUE(context_->Initialize(
-        surface.get(), GenerateGLContextAttribs(attribs, context_group)));
+        surface.get(),
+        GenerateGLContextAttribsForDecoder(attribs, context_group)));
   } else {
     if (real_gl_context) {
       context_ = scoped_refptr<gl::GLContext>(new gpu::GLContextVirtual(
           share_group_.get(), real_gl_context, decoder_->AsWeakPtr()));
       ASSERT_TRUE(context_->Initialize(
-          surface.get(), GenerateGLContextAttribs(attribs, context_group)));
+          surface.get(),
+          GenerateGLContextAttribsForDecoder(attribs, context_group)));
     } else {
       context_ = gl::init::CreateGLContext(
           share_group_.get(), surface.get(),
-          GenerateGLContextAttribs(attribs, context_group));
+          GenerateGLContextAttribsForDecoder(attribs, context_group));
       g_gpu_feature_info.ApplyToGLContext(context_.get());
     }
   }
diff --git a/gpu/ipc/in_process_command_buffer.cc b/gpu/ipc/in_process_command_buffer.cc
index 160d3665..0c7d56b 100644
--- a/gpu/ipc/in_process_command_buffer.cc
+++ b/gpu/ipc/in_process_command_buffer.cc
@@ -365,7 +365,8 @@
       if (!real_context) {
         real_context = gl::init::CreateGLContext(
             gl_share_group_.get(), surface_.get(),
-            GenerateGLContextAttribs(*params.attribs, context_group_.get()));
+            GenerateGLContextAttribsForDecoder(*params.attribs,
+                                               context_group_.get()));
         if (!real_context) {
           // TODO(piman): This might not be fatal, we could recurse into
           // CreateGLContext to get more info, tho it should be exceedingly
@@ -398,7 +399,7 @@
         context_ = base::MakeRefCounted<GLContextVirtual>(
             gl_share_group_.get(), real_context.get(), decoder_->AsWeakPtr());
         if (!context_->Initialize(surface_.get(),
-                                  GenerateGLContextAttribs(
+                                  GenerateGLContextAttribsForDecoder(
                                       *params.attribs, context_group_.get()))) {
           // TODO(piman): This might not be fatal, we could recurse into
           // CreateGLContext to get more info, tho it should be exceedingly
diff --git a/gpu/ipc/in_process_gpu_thread_holder.cc b/gpu/ipc/in_process_gpu_thread_holder.cc
index 4f6ae6d2b7..75dc0a65 100644
--- a/gpu/ipc/in_process_gpu_thread_holder.cc
+++ b/gpu/ipc/in_process_gpu_thread_holder.cc
@@ -80,8 +80,8 @@
   share_group_ = new gl::GLShareGroup();
   surface_ =
       gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size());
-  gl::GLContextAttribs attribs = gles2::GenerateGLContextAttribs(
-      ContextCreationAttribs(), use_passthrough_cmd_decoder);
+  gl::GLContextAttribs attribs =
+      gles2::GenerateGLContextAttribsForCompositor(use_passthrough_cmd_decoder);
   context_ =
       gl::init::CreateGLContext(share_group_.get(), surface_.get(), attribs);
   CHECK(context_->MakeCurrent(surface_.get()));
diff --git a/gpu/ipc/service/gles2_command_buffer_stub.cc b/gpu/ipc/service/gles2_command_buffer_stub.cc
index 1c2e3d4..1beed846 100644
--- a/gpu/ipc/service/gles2_command_buffer_stub.cc
+++ b/gpu/ipc/service/gles2_command_buffer_stub.cc
@@ -266,7 +266,8 @@
     if (!context) {
       context = gl::init::CreateGLContext(
           share_group_.get(), surface_.get(),
-          GenerateGLContextAttribs(init_params.attribs, context_group_.get()));
+          GenerateGLContextAttribsForDecoder(init_params.attribs,
+                                             context_group_.get()));
       if (!context) {
         // TODO(piman): This might not be fatal, we could recurse into
         // CreateGLContext to get more info, tho it should be exceedingly
@@ -293,8 +294,8 @@
     context = base::MakeRefCounted<GLContextVirtual>(
         share_group_.get(), context.get(), gles2_decoder_->AsWeakPtr());
     if (!context->Initialize(surface_.get(),
-                             GenerateGLContextAttribs(init_params.attribs,
-                                                      context_group_.get()))) {
+                             GenerateGLContextAttribsForDecoder(
+                                 init_params.attribs, context_group_.get()))) {
       // The real context created above for the default offscreen surface
       // might not be compatible with this surface.
       context = nullptr;
@@ -308,7 +309,8 @@
   } else {
     context = gl::init::CreateGLContext(
         share_group_.get(), surface_.get(),
-        GenerateGLContextAttribs(init_params.attribs, context_group_.get()));
+        GenerateGLContextAttribsForDecoder(init_params.attribs,
+                                           context_group_.get()));
     if (!context) {
       // TODO(piman): This might not be fatal, we could recurse into
       // CreateGLContext to get more info, tho it should be exceedingly
diff --git a/gpu/ipc/service/gpu_channel_manager.cc b/gpu/ipc/service/gpu_channel_manager.cc
index 68ae943..ed7b7d65 100644
--- a/gpu/ipc/service/gpu_channel_manager.cc
+++ b/gpu/ipc/service/gpu_channel_manager.cc
@@ -929,12 +929,8 @@
     context = nullptr;
   }
   if (!context) {
-    ContextCreationAttribs attribs_helper;
-    attribs_helper.context_type = features::UseGles2ForOopR()
-                                      ? gpu::CONTEXT_TYPE_OPENGLES2
-                                      : gpu::CONTEXT_TYPE_OPENGLES3;
-    gl::GLContextAttribs attribs = gles2::GenerateGLContextAttribs(
-        attribs_helper, use_passthrough_decoder);
+    gl::GLContextAttribs attribs =
+        gles2::GenerateGLContextAttribsForCompositor(use_passthrough_decoder);
 
     // Disable robust resource initialization for raster decoder and compositor.
     // TODO(crbug.com/1192632): disable robust_resource_initialization for
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 4898966..d7169fd 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -75164,6 +75164,10 @@
         value: 10
       }
       experiments {
+        key: "chromium.compilator_can_outlive_parent"
+        value: 100
+      }
+      experiments {
         key: "chromium_swarming.expose_merge_script_failures"
         value: 100
       }
@@ -87711,7 +87715,7 @@
       }
       experiments {
         key: "chromium.enable_cleandead"
-        value: 10
+        value: 50
       }
       experiments {
         key: "chromium_swarming.expose_merge_script_failures"
@@ -90565,6 +90569,10 @@
         value: 10
       }
       experiments {
+        key: "chromium.compilator_can_outlive_parent"
+        value: 100
+      }
+      experiments {
         key: "chromium_swarming.expose_merge_script_failures"
         value: 100
       }
@@ -92340,7 +92348,7 @@
       name: "mac-angle-chromium-try"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-angle-chromium-try"
-      dimensions: "cpu:x86-64"
+      dimensions: "cpu:arm64"
       dimensions: "os:Mac"
       dimensions: "pool:luci.chromium.try"
       exe {
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 1d7681f7..78217aa0 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -70,16 +70,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 121.0.6127.0',
+    'description': 'Run with ash-chrome version 121.0.6128.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v121.0.6127.0',
-          'revision': 'version:121.0.6127.0',
+          'location': 'lacros_version_skew_tests_v121.0.6128.0',
+          'revision': 'version:121.0.6128.0',
         },
       ],
     },
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.angle.star b/infra/config/subprojects/chromium/try/tryserver.chromium.angle.star
index 01b9dc74..47ca374 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.angle.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.angle.star
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 """Definitions of builders in the tryserver.chromium.angle builder group."""
 
-load("//lib/builders.star", "os", "reclient")
+load("//lib/builders.star", "cpu", "os", "reclient")
 load("//lib/builder_config.star", "builder_config")
 load("//lib/consoles.star", "consoles")
 load("//lib/try.star", "try_")
@@ -75,6 +75,7 @@
     ),
     cores = None,
     os = os.MAC_ANY,
+    cpu = cpu.ARM64,
 )
 
 try_.builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
index 2d72bf4..4cef4c1f 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
@@ -182,6 +182,7 @@
     experiments = {
         # go/nplus1shardsproposal
         "chromium.add_one_test_shard": 10,
+        "chromium.compilator_can_outlive_parent": 100,
     },
     main_list_view = "try",
     tryjob = try_.job(),
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
index a25f56a..779fb79 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
@@ -296,7 +296,7 @@
     branch_selector = branches.selector.LINUX_BRANCHES,
     experiments = {
         # crbug/940930
-        "chromium.enable_cleandead": 10,
+        "chromium.enable_cleandead": 50,
     },
     main_list_view = "try",
 )
@@ -488,6 +488,7 @@
     experiments = {
         # go/nplus1shardsproposal
         "chromium.add_one_test_shard": 10,
+        "chromium.compilator_can_outlive_parent": 100,
     },
     main_list_view = "try",
     tryjob = try_.job(),
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index 8e7cb458..975085fb 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -1,16 +1,16 @@
 {
   "LACROS_VERSION_SKEW_CANARY": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 121.0.6127.0",
+    "description": "Run with ash-chrome version 121.0.6128.0",
     "identifier": "Lacros version skew testing ash canary",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v121.0.6127.0",
-          "revision": "version:121.0.6127.0"
+          "location": "lacros_version_skew_tests_v121.0.6128.0",
+          "revision": "version:121.0.6128.0"
         }
       ]
     }
diff --git a/ios/chrome/app/application_delegate/BUILD.gn b/ios/chrome/app/application_delegate/BUILD.gn
index fb8f97a..446cedc 100644
--- a/ios/chrome/app/application_delegate/BUILD.gn
+++ b/ios/chrome/app/application_delegate/BUILD.gn
@@ -47,7 +47,7 @@
     "//ios/chrome/app:safe_mode_app_state_agent",
     "//ios/chrome/app/startup",
     "//ios/chrome/browser/crash_report/model",
-    "//ios/chrome/browser/device_sharing",
+    "//ios/chrome/browser/device_sharing/model",
     "//ios/chrome/browser/geolocation/model",
     "//ios/chrome/browser/metrics",
     "//ios/chrome/browser/shared/coordinator/scene",
@@ -186,7 +186,7 @@
     "//ios/chrome/browser/browsing_data/model",
     "//ios/chrome/browser/crash_report/model",
     "//ios/chrome/browser/default_browser/model:utils",
-    "//ios/chrome/browser/device_sharing",
+    "//ios/chrome/browser/device_sharing/model",
     "//ios/chrome/browser/enterprise/model/idle",
     "//ios/chrome/browser/feature_engagement/model",
     "//ios/chrome/browser/geolocation/model",
diff --git a/ios/chrome/app/application_delegate/app_state.mm b/ios/chrome/app/application_delegate/app_state.mm
index a14b7cbc..adccf0cf 100644
--- a/ios/chrome/app/application_delegate/app_state.mm
+++ b/ios/chrome/app/application_delegate/app_state.mm
@@ -31,7 +31,7 @@
 #import "ios/chrome/browser/crash_report/model/crash_keys_helper.h"
 #import "ios/chrome/browser/crash_report/model/crash_loop_detection_util.h"
 #import "ios/chrome/browser/crash_report/model/features.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager.h"
 #import "ios/chrome/browser/enterprise/model/idle/idle_service_factory.h"
 #import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
 #import "ios/chrome/browser/metrics/model/web_state_list_metrics_browser_agent.h"
diff --git a/ios/chrome/app/application_delegate/app_state_unittest.mm b/ios/chrome/app/application_delegate/app_state_unittest.mm
index f202529..a933d61 100644
--- a/ios/chrome/app/application_delegate/app_state_unittest.mm
+++ b/ios/chrome/app/application_delegate/app_state_unittest.mm
@@ -23,7 +23,7 @@
 #import "ios/chrome/app/safe_mode_app_state_agent+private.h"
 #import "ios/chrome/app/safe_mode_app_state_agent.h"
 #import "ios/chrome/browser/crash_report/model/crash_helper.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager.h"
 #import "ios/chrome/browser/shared/coordinator/scene/connection_information.h"
 #import "ios/chrome/browser/shared/coordinator/scene/test/fake_scene_state.h"
 #import "ios/chrome/browser/shared/coordinator/scene/test/stub_browser_provider.h"
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 40319302..7bb6c1e5 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -2277,13 +2277,13 @@
       </message>
       <message name="IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_TRACK_BUTTON" desc="Text for the button in the parcel tracking infobar modal that tracks the package(s).">
         {COUNT, plural,
-        =1 {Track this package}
-        other {Track all packages}}
+        =1 {Track This Package}
+        other {Track All Packages}}
       </message>
       <message name="IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_UNTRACK_BUTTON" desc="Text for the button in the parcel tracking infobar modal that untracks the package(s).">
         {COUNT, plural,
-        =1 {Untrack this package}
-        other {Untrack all packages}}
+        =1 {Untrack This Package}
+        other {Untrack All Packages}}
       </message>
       <message name="IDS_IOS_PARCEL_TRACKING_OPT_IN_PRIMARY_ACTION" desc="Text for the primary action string of the parcel tracking opt-in half-sheet screen.">
         Always Track Detected Packages
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_TRACK_BUTTON.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_TRACK_BUTTON.png.sha1
index 4741b83..6a7d424 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_TRACK_BUTTON.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_TRACK_BUTTON.png.sha1
@@ -1 +1 @@
-074bc5d1872a1f5476b59a524ed0c5f228927aa8
\ No newline at end of file
+86a26fd7bb933cf4a4e95db3f97cdfb2fdf80cf8
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_UNTRACK_BUTTON.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_UNTRACK_BUTTON.png.sha1
index 3f1e165..7a16f004 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_UNTRACK_BUTTON.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PARCEL_TRACKING_MODAL_INFOBAR_UNTRACK_BUTTON.png.sha1
@@ -1 +1 @@
-33a344cf246add9749a1f740e38205babe92cfa5
\ No newline at end of file
+a49c86b9e53b13d6d1c5ccc85c4e633b4edf6359
\ No newline at end of file
diff --git a/ios/chrome/browser/browser_state/model/BUILD.gn b/ios/chrome/browser/browser_state/model/BUILD.gn
index 380470f..5aeeb06 100644
--- a/ios/chrome/browser/browser_state/model/BUILD.gn
+++ b/ios/chrome/browser/browser_state/model/BUILD.gn
@@ -82,7 +82,7 @@
     "//ios/chrome/browser/content_settings/model",
     "//ios/chrome/browser/crash_report/model/breadcrumbs",
     "//ios/chrome/browser/credential_provider/model:buildflags",
-    "//ios/chrome/browser/device_sharing",
+    "//ios/chrome/browser/device_sharing/model",
     "//ios/chrome/browser/discover_feed:discover_feed_factory",
     "//ios/chrome/browser/dom_distiller/model",
     "//ios/chrome/browser/download/model",
diff --git a/ios/chrome/browser/browser_state/model/browser_state_keyed_service_factories.mm b/ios/chrome/browser/browser_state/model/browser_state_keyed_service_factories.mm
index 232c9c729..1c4c7f31 100644
--- a/ios/chrome/browser/browser_state/model/browser_state_keyed_service_factories.mm
+++ b/ios/chrome/browser/browser_state/model/browser_state_keyed_service_factories.mm
@@ -22,7 +22,7 @@
 #import "ios/chrome/browser/content_settings/model/cookie_settings_factory.h"
 #import "ios/chrome/browser/crash_report/model/breadcrumbs/breadcrumb_manager_keyed_service_factory.h"
 #import "ios/chrome/browser/credential_provider/model/credential_provider_buildflags.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_factory.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.h"
 #import "ios/chrome/browser/discover_feed/discover_feed_service_factory.h"
 #import "ios/chrome/browser/dom_distiller/model/dom_distiller_service_factory.h"
 #import "ios/chrome/browser/download/model/background_service/background_download_service_factory.h"
diff --git a/ios/chrome/browser/device_sharing/BUILD.gn b/ios/chrome/browser/device_sharing/model/BUILD.gn
similarity index 96%
rename from ios/chrome/browser/device_sharing/BUILD.gn
rename to ios/chrome/browser/device_sharing/model/BUILD.gn
index 2c6188c..705f34e 100644
--- a/ios/chrome/browser/device_sharing/BUILD.gn
+++ b/ios/chrome/browser/device_sharing/model/BUILD.gn
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-source_set("device_sharing") {
+source_set("model") {
   sources = [
     "device_sharing_browser_agent.h",
     "device_sharing_browser_agent.mm",
@@ -34,7 +34,7 @@
     "device_sharing_manager_impl_unittest.mm",
   ]
   deps = [
-    ":device_sharing",
+    ":model",
     "//base",
     "//components/handoff",
     "//components/sync_preferences:test_support",
@@ -56,7 +56,7 @@
     "handoff_manager_app_interface.mm",
   ]
   deps = [
-    ":device_sharing",
+    ":model",
     "//components/handoff",
     "//ios/chrome/test/app:test_support",
   ]
diff --git a/ios/chrome/browser/device_sharing/DEPS b/ios/chrome/browser/device_sharing/model/DEPS
similarity index 100%
rename from ios/chrome/browser/device_sharing/DEPS
rename to ios/chrome/browser/device_sharing/model/DEPS
diff --git a/ios/chrome/browser/device_sharing/OWNERS b/ios/chrome/browser/device_sharing/model/OWNERS
similarity index 100%
rename from ios/chrome/browser/device_sharing/OWNERS
rename to ios/chrome/browser/device_sharing/model/OWNERS
diff --git a/ios/chrome/browser/device_sharing/device_sharing_browser_agent.h b/ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.h
similarity index 90%
rename from ios/chrome/browser/device_sharing/device_sharing_browser_agent.h
rename to ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.h
index aa0613e..c256f0be 100644
--- a/ios/chrome/browser/device_sharing/device_sharing_browser_agent.h
+++ b/ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_BROWSER_AGENT_H_
-#define IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_BROWSER_AGENT_H_
+#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_BROWSER_AGENT_H_
+#define IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_BROWSER_AGENT_H_
 
 #import "ios/chrome/browser/shared/model/browser/browser_observer.h"
 #import "ios/chrome/browser/shared/model/browser/browser_user_data.h"
@@ -65,4 +65,4 @@
       active_web_state_observer_;
 };
 
-#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_BROWSER_AGENT_H_
+#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_BROWSER_AGENT_H_
diff --git a/ios/chrome/browser/device_sharing/device_sharing_browser_agent.mm b/ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.mm
similarity index 92%
rename from ios/chrome/browser/device_sharing/device_sharing_browser_agent.mm
rename to ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.mm
index df564050..9491313 100644
--- a/ios/chrome/browser/device_sharing/device_sharing_browser_agent.mm
+++ b/ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.mm
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/device_sharing/device_sharing_browser_agent.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_factory.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/web_state_list/active_web_state_observation_forwarder.h"
diff --git a/ios/chrome/browser/device_sharing/device_sharing_browser_agent_unittest.mm b/ios/chrome/browser/device_sharing/model/device_sharing_browser_agent_unittest.mm
similarity index 97%
rename from ios/chrome/browser/device_sharing/device_sharing_browser_agent_unittest.mm
rename to ios/chrome/browser/device_sharing/model/device_sharing_browser_agent_unittest.mm
index 5971400..e05ff99 100644
--- a/ios/chrome/browser/device_sharing/device_sharing_browser_agent_unittest.mm
+++ b/ios/chrome/browser/device_sharing/model/device_sharing_browser_agent_unittest.mm
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/device_sharing/device_sharing_browser_agent.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.h"
 
 #import <memory>
 
 #import "components/handoff/handoff_manager.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_factory.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_impl.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
diff --git a/ios/chrome/browser/device_sharing/device_sharing_manager.h b/ios/chrome/browser/device_sharing/model/device_sharing_manager.h
similarity index 85%
rename from ios/chrome/browser/device_sharing/device_sharing_manager.h
rename to ios/chrome/browser/device_sharing/model/device_sharing_manager.h
index 62a2e3d..689d7da 100644
--- a/ios/chrome/browser/device_sharing/device_sharing_manager.h
+++ b/ios/chrome/browser/device_sharing/model/device_sharing_manager.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_MANAGER_H_
-#define IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_MANAGER_H_
+#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_MANAGER_H_
+#define IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_MANAGER_H_
 
 #include <string>
 
@@ -37,4 +37,4 @@
   virtual void ClearActiveUrl(Browser* browser) = 0;
 };
 
-#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_MANAGER_H_
+#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_MANAGER_H_
diff --git a/ios/chrome/browser/device_sharing/device_sharing_manager_factory.h b/ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.h
similarity index 85%
rename from ios/chrome/browser/device_sharing/device_sharing_manager_factory.h
rename to ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.h
index bdf405d..364c75e8 100644
--- a/ios/chrome/browser/device_sharing/device_sharing_manager_factory.h
+++ b/ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_MANAGER_FACTORY_H_
-#define IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_MANAGER_FACTORY_H_
+#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_MANAGER_FACTORY_H_
+#define IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_MANAGER_FACTORY_H_
 
 #include "base/no_destructor.h"
 #include "components/keyed_service/ios/browser_state_keyed_service_factory.h"
@@ -43,4 +43,4 @@
       web::BrowserState* context) const override;
 };
 
-#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_MANAGER_FACTORY_H_
+#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_MANAGER_FACTORY_H_
diff --git a/ios/chrome/browser/device_sharing/device_sharing_manager_factory.mm b/ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.mm
similarity index 88%
rename from ios/chrome/browser/device_sharing/device_sharing_manager_factory.mm
rename to ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.mm
index 79b67e9..418bf8b 100644
--- a/ios/chrome/browser/device_sharing/device_sharing_manager_factory.mm
+++ b/ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.mm
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_factory.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.h"
 
 #import "base/no_destructor.h"
 #import "components/keyed_service/ios/browser_state_dependency_manager.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_impl.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.h"
 #import "ios/chrome/browser/shared/model/browser_state/browser_state_otr_helper.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 
diff --git a/ios/chrome/browser/device_sharing/device_sharing_manager_impl.h b/ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.h
similarity index 83%
rename from ios/chrome/browser/device_sharing/device_sharing_manager_impl.h
rename to ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.h
index 914d583..5fc091b 100644
--- a/ios/chrome/browser/device_sharing/device_sharing_manager_impl.h
+++ b/ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_MANAGER_IMPL_H_
-#define IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_MANAGER_IMPL_H_
+#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_MANAGER_IMPL_H_
+#define IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_MANAGER_IMPL_H_
 
 #import <memory>
 
 #include "base/gtest_prod_util.h"
 #import "components/prefs/pref_change_registrar.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager.h"
 
 class Browser;
 class ChromeBrowserState;
@@ -50,4 +50,4 @@
   Browser* active_browser_ = nullptr;
 };
 
-#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_DEVICE_SHARING_MANAGER_IMPL_H_
+#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_DEVICE_SHARING_MANAGER_IMPL_H_
diff --git a/ios/chrome/browser/device_sharing/device_sharing_manager_impl.mm b/ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.mm
similarity index 96%
rename from ios/chrome/browser/device_sharing/device_sharing_manager_impl.mm
rename to ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.mm
index ae36c00..b520c30cd 100644
--- a/ios/chrome/browser/device_sharing/device_sharing_manager_impl.mm
+++ b/ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_impl.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.h"
 
 #import "components/handoff/handoff_manager.h"
 #import "components/handoff/pref_names_ios.h"
diff --git a/ios/chrome/browser/device_sharing/device_sharing_manager_impl_unittest.mm b/ios/chrome/browser/device_sharing/model/device_sharing_manager_impl_unittest.mm
similarity index 95%
rename from ios/chrome/browser/device_sharing/device_sharing_manager_impl_unittest.mm
rename to ios/chrome/browser/device_sharing/model/device_sharing_manager_impl_unittest.mm
index 847d5b5..f0c9a1a 100644
--- a/ios/chrome/browser/device_sharing/device_sharing_manager_impl_unittest.mm
+++ b/ios/chrome/browser/device_sharing/model/device_sharing_manager_impl_unittest.mm
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager.h"
 
 #import <memory>
 
 #import "components/handoff/handoff_manager.h"
 #import "components/handoff/pref_names_ios.h"
 #import "components/sync_preferences/testing_pref_service_syncable.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_factory.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_impl.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
 #import "ios/web/public/test/web_task_environment.h"
diff --git a/ios/chrome/browser/device_sharing/handoff_manager_app_interface.h b/ios/chrome/browser/device_sharing/model/handoff_manager_app_interface.h
similarity index 68%
rename from ios/chrome/browser/device_sharing/handoff_manager_app_interface.h
rename to ios/chrome/browser/device_sharing/model/handoff_manager_app_interface.h
index 5b0b623..d7be013 100644
--- a/ios/chrome/browser/device_sharing/handoff_manager_app_interface.h
+++ b/ios/chrome/browser/device_sharing/model/handoff_manager_app_interface.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_HANDOFF_MANAGER_APP_INTERFACE_H_
-#define IOS_CHROME_BROWSER_DEVICE_SHARING_HANDOFF_MANAGER_APP_INTERFACE_H_
+#ifndef IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_HANDOFF_MANAGER_APP_INTERFACE_H_
+#define IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_HANDOFF_MANAGER_APP_INTERFACE_H_
 
 #import <UIKit/UIKit.h>
 
@@ -22,4 +22,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_HANDOFF_MANAGER_APP_INTERFACE_H_
+#endif  // IOS_CHROME_BROWSER_DEVICE_SHARING_MODEL_HANDOFF_MANAGER_APP_INTERFACE_H_
diff --git a/ios/chrome/browser/device_sharing/handoff_manager_app_interface.mm b/ios/chrome/browser/device_sharing/model/handoff_manager_app_interface.mm
similarity index 76%
rename from ios/chrome/browser/device_sharing/handoff_manager_app_interface.mm
rename to ios/chrome/browser/device_sharing/model/handoff_manager_app_interface.mm
index caf18d69..462b68b 100644
--- a/ios/chrome/browser/device_sharing/handoff_manager_app_interface.mm
+++ b/ios/chrome/browser/device_sharing/model/handoff_manager_app_interface.mm
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/device_sharing/handoff_manager_app_interface.h"
+#import "ios/chrome/browser/device_sharing/model/handoff_manager_app_interface.h"
 
 #import "components/handoff/handoff_manager.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_factory.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_manager_impl.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_factory.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_manager_impl.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
 
 NSURL* DeviceSharingAppInterfaceWrapper::GetCurrentUserActivityURL(
diff --git a/ios/chrome/browser/device_sharing/handoff_manager_app_interface_stub.mm b/ios/chrome/browser/device_sharing/model/handoff_manager_app_interface_stub.mm
similarity index 75%
rename from ios/chrome/browser/device_sharing/handoff_manager_app_interface_stub.mm
rename to ios/chrome/browser/device_sharing/model/handoff_manager_app_interface_stub.mm
index 7cbac73..7ff3b6c 100644
--- a/ios/chrome/browser/device_sharing/handoff_manager_app_interface_stub.mm
+++ b/ios/chrome/browser/device_sharing/model/handoff_manager_app_interface_stub.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/chrome/browser/device_sharing/handoff_manager_app_interface.h"
+#import "ios/chrome/browser/device_sharing/model/handoff_manager_app_interface.h"
 
 #import "ios/testing/earl_grey/earl_grey_test.h"
 
diff --git a/ios/chrome/browser/device_sharing/handoff_manager_egtest.mm b/ios/chrome/browser/device_sharing/model/handoff_manager_egtest.mm
similarity index 97%
rename from ios/chrome/browser/device_sharing/handoff_manager_egtest.mm
rename to ios/chrome/browser/device_sharing/model/handoff_manager_egtest.mm
index 0cf8be5e..a5e71ff 100644
--- a/ios/chrome/browser/device_sharing/handoff_manager_egtest.mm
+++ b/ios/chrome/browser/device_sharing/model/handoff_manager_egtest.mm
@@ -4,7 +4,7 @@
 
 #import <XCTest/XCTest.h>
 
-#import "ios/chrome/browser/device_sharing/handoff_manager_app_interface.h"
+#import "ios/chrome/browser/device_sharing/model/handoff_manager_app_interface.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #import "ios/testing/earl_grey/earl_grey_test.h"
diff --git a/ios/chrome/browser/favicon/model/BUILD.gn b/ios/chrome/browser/favicon/model/BUILD.gn
new file mode 100644
index 0000000..bc8f842
--- /dev/null
+++ b/ios/chrome/browser/favicon/model/BUILD.gn
@@ -0,0 +1,13 @@
+# 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.
+
+source_set("model") {
+  sources = [
+    "favicon_loader.h",
+    "favicon_service_factory.h",
+    "ios_chrome_favicon_loader_factory.h",
+    "ios_chrome_large_icon_service_factory.h",
+  ]
+  public_deps = [ "//ios/chrome/browser/favicon" ]
+}
diff --git a/ios/chrome/browser/favicon/model/favicon_loader.h b/ios/chrome/browser/favicon/model/favicon_loader.h
new file mode 100644
index 0000000..89d427a
--- /dev/null
+++ b/ios/chrome/browser/favicon/model/favicon_loader.h
@@ -0,0 +1,10 @@
+// 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 IOS_CHROME_BROWSER_FAVICON_MODEL_FAVICON_LOADER_H_
+#define IOS_CHROME_BROWSER_FAVICON_MODEL_FAVICON_LOADER_H_
+
+#import "ios/chrome/browser/favicon/favicon_loader.h"
+
+#endif  // IOS_CHROME_BROWSER_FAVICON_MODEL_FAVICON_LOADER_H_
diff --git a/ios/chrome/browser/favicon/model/favicon_service_factory.h b/ios/chrome/browser/favicon/model/favicon_service_factory.h
new file mode 100644
index 0000000..cd60bea
--- /dev/null
+++ b/ios/chrome/browser/favicon/model/favicon_service_factory.h
@@ -0,0 +1,10 @@
+// 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 IOS_CHROME_BROWSER_FAVICON_MODEL_FAVICON_SERVICE_FACTORY_H_
+#define IOS_CHROME_BROWSER_FAVICON_MODEL_FAVICON_SERVICE_FACTORY_H_
+
+#import "ios/chrome/browser/favicon/favicon_service_factory.h"
+
+#endif  // IOS_CHROME_BROWSER_FAVICON_MODEL_FAVICON_SERVICE_FACTORY_H_
diff --git a/ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h b/ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h
new file mode 100644
index 0000000..83bf42d
--- /dev/null
+++ b/ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h
@@ -0,0 +1,10 @@
+// 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 IOS_CHROME_BROWSER_FAVICON_MODEL_IOS_CHROME_FAVICON_LOADER_FACTORY_H_
+#define IOS_CHROME_BROWSER_FAVICON_MODEL_IOS_CHROME_FAVICON_LOADER_FACTORY_H_
+
+#import "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
+
+#endif  // IOS_CHROME_BROWSER_FAVICON_MODEL_IOS_CHROME_FAVICON_LOADER_FACTORY_H_
diff --git a/ios/chrome/browser/favicon/model/ios_chrome_large_icon_service_factory.h b/ios/chrome/browser/favicon/model/ios_chrome_large_icon_service_factory.h
new file mode 100644
index 0000000..c91a43e
--- /dev/null
+++ b/ios/chrome/browser/favicon/model/ios_chrome_large_icon_service_factory.h
@@ -0,0 +1,10 @@
+// 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 IOS_CHROME_BROWSER_FAVICON_MODEL_IOS_CHROME_LARGE_ICON_SERVICE_FACTORY_H_
+#define IOS_CHROME_BROWSER_FAVICON_MODEL_IOS_CHROME_LARGE_ICON_SERVICE_FACTORY_H_
+
+#import "ios/chrome/browser/favicon/ios_chrome_large_icon_service_factory.h"
+
+#endif  // IOS_CHROME_BROWSER_FAVICON_MODEL_IOS_CHROME_LARGE_ICON_SERVICE_FACTORY_H_
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 07552def0..fe0ad6a 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -1636,6 +1636,12 @@
     {"tab-groups-in-grid", flag_descriptions::kTabGroupsInGridName,
      flag_descriptions::kTabGroupsInGridDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kTabGroupsInGrid)},
+    {"autofill-enable-payments-mandatory-reauth",
+     flag_descriptions::kAutofillEnablePaymentsMandatoryReauthName,
+     flag_descriptions::kAutofillEnablePaymentsMandatoryReauthDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillEnablePaymentsMandatoryReauth)},
 };
 
 bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index b12ef1d..77451ee 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -57,6 +57,13 @@
     "merchant_domain parameter populated with the last origin of the main "
     "frame.";
 
+const char kAutofillEnablePaymentsMandatoryReauthName[] =
+    "Enable mandatory re-auth for payments autofill";
+const char kAutofillEnablePaymentsMandatoryReauthDescription[] =
+    "When this is enabled, in use-cases where we would not have triggered any "
+    "user-visible authentication to autofill payment methods, we will trigger "
+    "a device authentication.";
+
 const char kAutofillEnablePaymentsMandatoryReauthOnBlingName[] =
     "Enable mandatory re-auth for payments autofill on Bling";
 const char kAutofillEnablePaymentsMandatoryReauthOnBlingDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 6900c7f8..3b0bc8d8 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -52,6 +52,11 @@
 extern const char kAutofillEnableMerchantDomainInUnmaskCardRequestDescription[];
 
 // Title and description for the flag to control whether the autofill payments
+// mandatory reauth feature is enabled.
+extern const char kAutofillEnablePaymentsMandatoryReauthName[];
+extern const char kAutofillEnablePaymentsMandatoryReauthDescription[];
+
+// Title and description for the flag to control whether the autofill payments
 // mandatory reauth feature is enabled on Bling.
 extern const char kAutofillEnablePaymentsMandatoryReauthOnBlingName[];
 extern const char kAutofillEnablePaymentsMandatoryReauthOnBlingDescription[];
diff --git a/ios/chrome/browser/lens/lens_tab_helper.mm b/ios/chrome/browser/lens/lens_tab_helper.mm
index 87f7bee8..47fcdd4 100644
--- a/ios/chrome/browser/lens/lens_tab_helper.mm
+++ b/ios/chrome/browser/lens/lens_tab_helper.mm
@@ -24,6 +24,9 @@
 // The path for the search translate box entry point.
 NSString* const kTranslateOneboxEntryPointPath = @"/translate-onebox";
 
+// The path for the web image search bar entry point.
+NSString* const kWebImagesSearchBarEntryPointPath = @"/web-images-search-box";
+
 }  // namespace
 
 LensTabHelper::LensTabHelper(web::WebState* web_state)
@@ -43,6 +46,9 @@
   } else if ([path caseInsensitiveCompare:kTranslateOneboxEntryPointPath] ==
              NSOrderedSame) {
     return LensEntrypoint::TranslateOnebox;
+  } else if ([path caseInsensitiveCompare:kWebImagesSearchBarEntryPointPath] ==
+             NSOrderedSame) {
+    return LensEntrypoint::WebImagesSearchBar;
   }
   return std::nullopt;
 }
diff --git a/ios/chrome/browser/lens/lens_tab_helper_unittest.mm b/ios/chrome/browser/lens/lens_tab_helper_unittest.mm
index dc05d12..ff83d033 100644
--- a/ios/chrome/browser/lens/lens_tab_helper_unittest.mm
+++ b/ios/chrome/browser/lens/lens_tab_helper_unittest.mm
@@ -126,6 +126,44 @@
   EXPECT_TRUE(request_policy.ShouldCancelNavigation());
 }
 
+// Test `ShouldAllowRequest` for the web images search bar.
+TEST_F(LensTabHelperTest, ShouldAllowRequest_WebImagesSearchBar) {
+  NSURLRequest* request = [NSURLRequest
+      requestWithURL:[NSURL URLWithString:@"googlechromeaction://lens/"
+                                          @"web-images-search-box"]];
+  const web::WebStatePolicyDecider::RequestInfo request_info(
+      ui::PageTransition::PAGE_TRANSITION_LINK,
+      /*target_frame_is_main=*/true,
+      /*target_frame_is_cross_origin=*/false,
+      /*has_user_gesture=*/false);
+  __block bool callback_called = false;
+  __block web::WebStatePolicyDecider::PolicyDecision request_policy =
+      web::WebStatePolicyDecider::PolicyDecision::Allow();
+  auto callback =
+      base::BindOnce(^(web::WebStatePolicyDecider::PolicyDecision decision) {
+        request_policy = decision;
+        callback_called = true;
+      });
+
+  id mock_lens_commands_handler = OCMProtocolMock(@protocol(LensCommands));
+  [dispatcher_ startDispatchingToTarget:mock_lens_commands_handler
+                            forProtocol:@protocol(LensCommands)];
+
+  OCMExpect([mock_lens_commands_handler
+      openLensInputSelection:[OCMArg
+                                 checkWithBlock:^(
+                                     OpenLensInputSelectionCommand* command) {
+                                   return command.entryPoint ==
+                                          LensEntrypoint::WebImagesSearchBar;
+                                 }]]);
+
+  helper_->ShouldAllowRequest(request, request_info, std::move(callback));
+
+  EXPECT_OCMOCK_VERIFY(mock_lens_commands_handler);
+  EXPECT_TRUE(callback_called);
+  EXPECT_TRUE(request_policy.ShouldCancelNavigation());
+}
+
 // Test `EntryPointForGoogleChromeActionURLPath` for the web search bar.
 TEST_F(LensTabHelperTest, EntryPointForGoogleChromeActionURLPath_WebSearchBar) {
   std::optional<LensEntrypoint> entry_point =
@@ -144,6 +182,16 @@
   EXPECT_EQ(entry_point.value(), LensEntrypoint::TranslateOnebox);
 }
 
+// Test `EntryPointForGoogleChromeActionURLPath` for the web images search bar.
+TEST_F(LensTabHelperTest,
+       EntryPointForGoogleChromeActionURLPath_WebImagesSearchBar) {
+  std::optional<LensEntrypoint> entry_point =
+      LensTabHelper::EntryPointForGoogleChromeActionURLPath(
+          @"/web-images-search-box");
+  ASSERT_TRUE(entry_point);
+  EXPECT_EQ(entry_point.value(), LensEntrypoint::WebImagesSearchBar);
+}
+
 // Test `EntryPointForGoogleChromeActionURLPath` for an invalid entry point.
 TEST_F(LensTabHelperTest, EntryPointForGoogleChromeActionURLPath_Invalid) {
   std::optional<LensEntrypoint> entry_point =
diff --git a/ios/chrome/browser/main/BUILD.gn b/ios/chrome/browser/main/BUILD.gn
index 734487e..497c3fc 100644
--- a/ios/chrome/browser/main/BUILD.gn
+++ b/ios/chrome/browser/main/BUILD.gn
@@ -21,7 +21,7 @@
     "//components/breadcrumbs/core:status",
     "//ios/chrome/browser/app_launcher/model",
     "//ios/chrome/browser/crash_report/model/breadcrumbs",
-    "//ios/chrome/browser/device_sharing",
+    "//ios/chrome/browser/device_sharing/model",
     "//ios/chrome/browser/favicon",
     "//ios/chrome/browser/follow:browser_agent",
     "//ios/chrome/browser/infobars/overlays/browser_agent:browser_agent_util",
diff --git a/ios/chrome/browser/main/DEPS b/ios/chrome/browser/main/DEPS
index d22c352..ca4a1856 100644
--- a/ios/chrome/browser/main/DEPS
+++ b/ios/chrome/browser/main/DEPS
@@ -11,7 +11,7 @@
     "+ios/chrome/browser/ui/start_surface/start_surface_recent_tab_browser_agent.h",
     "+ios/chrome/browser/app_launcher/model/app_launcher_browser_agent.h",
     "+ios/chrome/browser/crash_report/model/breadcrumbs/breadcrumb_manager_browser_agent.h",
-    "+ios/chrome/browser/device_sharing/device_sharing_browser_agent.h",
+    "+ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.h",
     "+ios/chrome/browser/follow/follow_browser_agent.h",
     "+ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_util.h",
     "+ios/chrome/browser/lens/lens_browser_agent.h",
diff --git a/ios/chrome/browser/main/browser_agent_util.mm b/ios/chrome/browser/main/browser_agent_util.mm
index e261f0ef..45b03d94 100644
--- a/ios/chrome/browser/main/browser_agent_util.mm
+++ b/ios/chrome/browser/main/browser_agent_util.mm
@@ -8,7 +8,7 @@
 #import "components/breadcrumbs/core/breadcrumbs_status.h"
 #import "ios/chrome/browser/app_launcher/model/app_launcher_browser_agent.h"
 #import "ios/chrome/browser/crash_report/model/breadcrumbs/breadcrumb_manager_browser_agent.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_browser_agent.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.h"
 #import "ios/chrome/browser/favicon/favicon_browser_agent.h"
 #import "ios/chrome/browser/follow/follow_browser_agent.h"
 #import "ios/chrome/browser/infobars/overlays/browser_agent/infobar_overlay_browser_agent_util.h"
diff --git a/ios/chrome/browser/ui/browser_view/BUILD.gn b/ios/chrome/browser/ui/browser_view/BUILD.gn
index 03a0a039..378279b 100644
--- a/ios/chrome/browser/ui/browser_view/BUILD.gn
+++ b/ios/chrome/browser/ui/browser_view/BUILD.gn
@@ -309,8 +309,6 @@
     "//ios/chrome/browser/search_engines/model",
     "//ios/chrome/browser/sessions",
     "//ios/chrome/browser/sessions:fake",
-    "//ios/chrome/browser/sessions:restoration_agent",
-    "//ios/chrome/browser/sessions:test_support",
     "//ios/chrome/browser/shared/coordinator/layout_guide",
     "//ios/chrome/browser/shared/coordinator/scene:scene_state_header",
     "//ios/chrome/browser/shared/model/browser/test:test_support",
@@ -361,7 +359,6 @@
     "//ios/chrome/test:test_support",
     "//ios/net",
     "//ios/public/provider/chrome/browser/user_feedback:user_feedback_api",
-    "//ios/web/common:features",
     "//ios/web/common:uikit",
     "//ios/web/find_in_page",
     "//ios/web/public",
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm
index 11f832c..d8ce050b5 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator_unittest.mm
@@ -17,8 +17,6 @@
 #import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
 #import "ios/chrome/browser/prerender/model/prerender_service_factory.h"
 #import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
-#import "ios/chrome/browser/sessions/session_restoration_browser_agent.h"
-#import "ios/chrome/browser/sessions/test_session_service.h"
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
@@ -62,18 +60,10 @@
 #import "third_party/ocmock/OCMock/OCMock.h"
 #import "third_party/ocmock/gtest_support.h"
 
-// To get access to web::features::kEnableSessionSerializationOptimizations.
-// TODO(crbug.com/1383087): remove once the feature is fully launched.
-#import "base/test/scoped_feature_list.h"
-#import "ios/web/common/features.h"
-
 // Test fixture for BrowserCoordinator testing.
 class BrowserCoordinatorTest : public PlatformTest {
  protected:
   BrowserCoordinatorTest() {
-    scoped_feature_list_.InitAndDisableFeature(
-        web::features::kEnableSessionSerializationOptimizations);
-
     base_view_controller_ = [[UIViewController alloc] init];
     scene_state_ = [[SceneState alloc] initWithAppState:nil];
 
@@ -118,10 +108,6 @@
     AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
         chrome_browser_state_.get(),
         std::make_unique<FakeAuthenticationServiceDelegate>());
-    SessionRestorationBrowserAgent::CreateForBrowser(
-        browser_.get(), [[TestSessionService alloc] init], false);
-    SessionRestorationBrowserAgent::FromBrowser(browser_.get())
-        ->SetSessionID([[NSUUID UUID] UUIDString]);
 
     IncognitoReauthSceneAgent* reauthAgent = [[IncognitoReauthSceneAgent alloc]
         initWithReauthModule:[[ReauthenticationModule alloc] init]];
@@ -188,7 +174,6 @@
     ntpHelper->PageLoaded(web_state, web::PageLoadCompletionStatus::SUCCESS);
   }
 
-  base::test::ScopedFeatureList scoped_feature_list_;
   IOSChromeScopedTestingLocalState local_state_;
   web::WebTaskEnvironment task_environment_;
   UIViewController* base_view_controller_;
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
index 4a837bb..ca362da 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller_unittest.mm
@@ -28,8 +28,6 @@
 #import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
 #import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
 #import "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
-#import "ios/chrome/browser/sessions/session_restoration_browser_agent.h"
-#import "ios/chrome/browser/sessions/test_session_service.h"
 #import "ios/chrome/browser/shared/coordinator/layout_guide/layout_guide_util.h"
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
 #import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
@@ -86,20 +84,12 @@
 #import "third_party/ocmock/gtest_support.h"
 #import "ui/base/device_form_factor.h"
 
-// To get access to web::features::kEnableSessionSerializationOptimizations.
-// TODO(crbug.com/1383087): remove once the feature is fully launched.
-#import "ios/web/common/features.h"
-
-#pragma mark -
-
-namespace {
 class BrowserViewControllerTest : public BlockCleanupTest {
  public:
  protected:
   BrowserViewControllerTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {supervised_user::kFilterWebsitesForSupervisedUsersOnDesktopAndIOS},
-        {web::features::kEnableSessionSerializationOptimizations});
+    scoped_feature_list_.InitAndEnableFeature(
+        supervised_user::kFilterWebsitesForSupervisedUsersOnDesktopAndIOS);
   }
 
   void SetUp() override {
@@ -155,10 +145,6 @@
     WebUsageEnablerBrowserAgent::FromBrowser(browser_.get())
         ->SetWebUsageEnabled(true);
 
-    SessionRestorationBrowserAgent::CreateForBrowser(
-        browser_.get(), [[TestSessionService alloc] init], false);
-    SessionRestorationBrowserAgent::FromBrowser(browser_.get())
-        ->SetSessionID([[NSUUID UUID] UUIDString]);
     WebStateUpdateBrowserAgent::CreateForBrowser(browser_.get());
 
     CommandDispatcher* dispatcher = browser_->GetCommandDispatcher();
@@ -577,5 +563,3 @@
   EXPECT_OCMOCK_VERIFY(container_view_mock);
   [NTPCoordinator_ stop];
 }
-
-}  // namespace
diff --git a/ios/chrome/browser/ui/lens/BUILD.gn b/ios/chrome/browser/ui/lens/BUILD.gn
index a0984dd..85fc9049 100644
--- a/ios/chrome/browser/ui/lens/BUILD.gn
+++ b/ios/chrome/browser/ui/lens/BUILD.gn
@@ -26,6 +26,7 @@
     "//ios/chrome/browser/shared/model/browser",
     "//ios/chrome/browser/shared/model/browser_state",
     "//ios/chrome/browser/shared/model/prefs:pref_names",
+    "//ios/chrome/browser/shared/model/url",
     "//ios/chrome/browser/shared/model/web_state_list",
     "//ios/chrome/browser/shared/public/commands",
     "//ios/chrome/browser/shared/public/features",
diff --git a/ios/chrome/browser/ui/lens/lens_coordinator.mm b/ios/chrome/browser/ui/lens/lens_coordinator.mm
index 2debcde6..bed70688 100644
--- a/ios/chrome/browser/ui/lens/lens_coordinator.mm
+++ b/ios/chrome/browser/ui/lens/lens_coordinator.mm
@@ -19,6 +19,7 @@
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
+#import "ios/chrome/browser/shared/model/url/url_util.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h"
 #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
@@ -407,10 +408,20 @@
 - (void)openWebLoadParams:(const web::NavigationManager::WebLoadParams&)params {
   if (!self.browser)
     return;
-  UrlLoadParams loadParams = UrlLoadParams::InNewTab(params);
-  loadParams.SetInBackground(NO);
+  web::WebState* webState =
+      self.browser->GetWebStateList()->GetActiveWebState();
+  UrlLoadParams loadParams;
+
+  // Open in the current tab if the current tab is a NTP.
+  if (webState && IsUrlNtp(webState->GetLastCommittedURL())) {
+    loadParams = UrlLoadParams::InCurrentTab(params);
+    self.loadingWebState = webState;
+  } else {
+    loadParams = UrlLoadParams::InNewTab(params);
+    loadParams.append_to = OpenPosition::kCurrentTab;
+    loadParams.SetInBackground(NO);
+  }
   loadParams.in_incognito = self.browser->GetBrowserState()->IsOffTheRecord();
-  loadParams.append_to = OpenPosition::kCurrentTab;
   UrlLoadingBrowserAgent::FromBrowser(self.browser)->Load(loadParams);
 }
 
diff --git a/ios/chrome/browser/ui/lens/lens_entrypoint.h b/ios/chrome/browser/ui/lens/lens_entrypoint.h
index 599f855..d33ce2c 100644
--- a/ios/chrome/browser/ui/lens/lens_entrypoint.h
+++ b/ios/chrome/browser/ui/lens/lens_entrypoint.h
@@ -21,7 +21,8 @@
   WebSearchBar = 9,
   TranslateOnebox = 10,
   Intents = 11,
-  kMaxValue = Intents,
+  WebImagesSearchBar = 12,
+  kMaxValue = WebImagesSearchBar,
 };
 
 extern const char kIOSLensEntrypoint[];
diff --git a/ios/chrome/browser/ui/main/BUILD.gn b/ios/chrome/browser/ui/main/BUILD.gn
index fed8de7..d5a974e 100644
--- a/ios/chrome/browser/ui/main/BUILD.gn
+++ b/ios/chrome/browser/ui/main/BUILD.gn
@@ -80,7 +80,7 @@
     "//ios/chrome/browser/autofill:autofill_internal",
     "//ios/chrome/browser/browsing_data/model",
     "//ios/chrome/browser/crash_report/model:model_internal",
-    "//ios/chrome/browser/device_sharing",
+    "//ios/chrome/browser/device_sharing/model",
     "//ios/chrome/browser/download/model",
     "//ios/chrome/browser/main",
     "//ios/chrome/browser/reading_list/model",
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler.mm b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
index 782bb8e..8eb8a29 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler.mm
@@ -11,7 +11,7 @@
 #import "base/strings/sys_string_conversions.h"
 #import "ios/chrome/app/application_delegate/app_state.h"
 #import "ios/chrome/browser/crash_report/model/crash_report_helper.h"
-#import "ios/chrome/browser/device_sharing/device_sharing_browser_agent.h"
+#import "ios/chrome/browser/device_sharing/model/device_sharing_browser_agent.h"
 #import "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
 #import "ios/chrome/browser/sessions/session_restoration_service.h"
 #import "ios/chrome/browser/sessions/session_restoration_service_factory.h"
diff --git a/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller.mm b/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller.mm
index 8c66ef6..5ffcc940 100644
--- a/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller.mm
+++ b/ios/chrome/browser/ui/parcel_tracking/parcel_tracking_opt_in_view_controller.mm
@@ -24,8 +24,8 @@
 NSString* const kOptInIcon = @"parcel_tracking_icon_new";
 // Radius size of the table view.
 CGFloat const kTableViewCornerRadius = 10;
-// Margin for the options view.
-CGFloat const kOptionsViewMargin = 17;
+// Horizontal margin for the view.
+CGFloat const kHorizontalMargin = 24;
 // Spacing before the image.
 CGFloat const kSpacingBeforeImage = 23;
 // Size of the radio buttons.
@@ -69,9 +69,9 @@
   // the top view.
   [NSLayoutConstraint activateConstraints:@[
     [optionsView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor
-                                              constant:kOptionsViewMargin],
+                                              constant:kHorizontalMargin],
     [optionsView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor
-                                               constant:-kOptionsViewMargin],
+                                               constant:-kHorizontalMargin],
   ]];
   [self updateButtonForState:UIControlStateDisabled];
 }
@@ -79,6 +79,8 @@
 - (void)viewWillLayoutSubviews {
   [super viewWillLayoutSubviews];
   [self updateOptionsViewHeightConstraint];
+  self.view.directionalLayoutMargins =
+      NSDirectionalEdgeInsetsMake(0, kHorizontalMargin, 0, kHorizontalMargin);
 }
 
 #pragma mark - ConfirmationAlertViewController
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn
index 9d32f72c..e9b5538 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/BUILD.gn
@@ -173,6 +173,8 @@
     "//ios/chrome/browser/ui/tab_switcher",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test:fakes",
     "//ios/chrome/browser/ui/tab_switcher/test:fakes",
     "//ios/chrome/test:test_support",
     "//ios/web/common:features",
@@ -213,6 +215,7 @@
     "//ios/chrome/browser/sync/model",
     "//ios/chrome/browser/sync/model:test_support",
     "//ios/chrome/browser/tabs/model",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test:fakes",
     "//ios/chrome/browser/ui/tab_switcher/test:fakes",
     "//ios/chrome/browser/web:feature_flags",
     "//ios/chrome/browser/web:page_placeholder",
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator_unittest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator_unittest.mm
index a6744d2..aed032b 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator_unittest.mm
@@ -14,6 +14,8 @@
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/incognito_grid_mediator.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular/regular_grid_mediator.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_configuration.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.h"
 #import "ios/chrome/browser/ui/tab_switcher/test/fake_tab_collection_consumer.h"
 #import "ios/web/public/test/fakes/fake_web_frames_manager.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
@@ -53,6 +55,7 @@
     }
     mediator_.consumer = consumer_;
     mediator_.browser = browser_.get();
+    mediator_.toolbarsMutator = fake_toolbars_mediator_;
   }
 
   void TearDown() override {
@@ -358,6 +361,27 @@
   EXPECT_EQ(0, user_action_tester_.GetActionCount(kHasPriceDropUserAction));
 }
 
+// Ensures that when there is web states in normal mode, the toolbar
+// configuration is correct.
+TEST_P(BaseGridMediatorTest, TestToolbarsNormalModeWithWebstates) {
+  EXPECT_EQ(3UL, consumer_.items.size());
+  // Force the toolbar configuration by setting the view as currently selected.
+  [mediator_ currentlySelectedGrid:YES];
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.closeAllButton);
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.doneButton);
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.newTabButton);
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.searchButton);
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.selectTabsButton);
+
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.undoButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.deselectAllButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.selectAllButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.addToButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.closeSelectedTabsButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.shareButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.cancelSearchButton);
+}
+
 INSTANTIATE_TEST_SUITE_P(
     /* No InstantiationName */,
     BaseGridMediatorTest,
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.h
index 15f00f4..5d7f046 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.h
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.h
@@ -21,6 +21,7 @@
 class Browser;
 class BrowserList;
 @class FakeTabCollectionConsumer;
+@class FakeTabGridToolbarsMediator;
 class GURL;
 class IOSChromeScopedTestingLocalState;
 class PlatformTest;
@@ -64,6 +65,7 @@
   BrowserList* browser_list_;
   base::UserActionTester user_action_tester_;
   AuthenticationService* auth_service_;
+  FakeTabGridToolbarsMediator* fake_toolbars_mediator_;
 };
 
 #endif  // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_GRID_GRID_MEDIATOR_TEST_H_
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.mm
index cf46ab2..63adb69d 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.mm
@@ -31,6 +31,7 @@
 #import "ios/chrome/browser/sync/model/sync_service_factory.h"
 #import "ios/chrome/browser/tabs/model/closing_web_state_observer_browser_agent.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.h"
 #import "ios/chrome/browser/ui/tab_switcher/test/fake_tab_collection_consumer.h"
 #import "ios/chrome/browser/web/features.h"
 #import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
@@ -145,6 +146,7 @@
   original_selected_identifier_ =
       browser_->GetWebStateList()->GetWebStateAt(1)->GetUniqueIdentifier();
   consumer_ = [[FakeTabCollectionConsumer alloc] init];
+  fake_toolbars_mediator_ = [[FakeTabGridToolbarsMediator alloc] init];
 }
 
 std::unique_ptr<web::FakeWebState>
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/BUILD.gn
index c78db46..841f67c 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/BUILD.gn
@@ -69,6 +69,8 @@
     "//ios/chrome/browser/shared/model/browser_state:test_support",
     "//ios/chrome/browser/shared/model/web_state_list",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid:test_fixture",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test:fakes",
     "//ios/chrome/browser/ui/tab_switcher/test:fakes",
     "//ios/web/public",
   ]
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/incognito_grid_mediator_unittest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/incognito_grid_mediator_unittest.mm
index 76046d69..bdd42dd8 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/incognito_grid_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/incognito/incognito_grid_mediator_unittest.mm
@@ -11,6 +11,8 @@
 #import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_configuration.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.h"
 #import "ios/chrome/browser/ui/tab_switcher/test/fake_tab_collection_consumer.h"
 #import "ios/web/public/web_state_id.h"
 
@@ -24,6 +26,7 @@
     mediator_ = [[IncognitoGridMediator alloc] init];
     mediator_.consumer = consumer_;
     mediator_.browser = browser_.get();
+    mediator_.toolbarsMutator = fake_toolbars_mediator_;
   }
 
   void TearDown() override {
@@ -83,3 +86,25 @@
       << "Can open an incognito tab by calling new tab button function when "
          "policy should disable incognito.";
 }
+
+// Ensures that when there is *no* web states in normal mode, the toolbar
+// configuration is correct.
+TEST_F(IncognitoGridMediatorTest, TestToolbarsNormalModeWithoutWebstates) {
+  EXPECT_EQ(3UL, consumer_.items.size());
+  [mediator_ closeAllItems];
+  EXPECT_EQ(0UL, consumer_.items.size());
+
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.newTabButton);
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.searchButton);
+
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.closeAllButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.doneButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.selectTabsButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.undoButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.deselectAllButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.selectAllButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.addToButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.closeSelectedTabsButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.shareButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.cancelSearchButton);
+}
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular/BUILD.gn
index 6b453b6..eac414de 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular/BUILD.gn
@@ -73,6 +73,8 @@
     "//ios/chrome/browser/shared/model/url:constants",
     "//ios/chrome/browser/shared/model/web_state_list",
     "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid:test_fixture",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test:fakes",
     "//ios/chrome/browser/ui/tab_switcher/test:fakes",
     "//ios/web/public/test/fakes",
   ]
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular/regular_grid_mediator_unittest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular/regular_grid_mediator_unittest.mm
index 92ffdb5e..e795668 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular/regular_grid_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/regular/regular_grid_mediator_unittest.mm
@@ -19,6 +19,8 @@
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_opener.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_mediator_test.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_configuration.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.h"
 #import "ios/chrome/browser/ui/tab_switcher/test/fake_tab_collection_consumer.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
 
@@ -32,6 +34,7 @@
     mediator_ = [[RegularGridMediator alloc] init];
     mediator_.consumer = consumer_;
     mediator_.browser = browser_.get();
+    mediator_.toolbarsMutator = fake_toolbars_mediator_;
 
     tab_restore_service_ =
         IOSChromeTabRestoreServiceFactory::GetForBrowserState(
@@ -190,3 +193,25 @@
       << "Can open a regular tab by calling new tab button function when "
          "policy force incognito only.";
 }
+
+// Ensures that when there is *no* web states in normal mode, the toolbar
+// configuration is correct.
+TEST_F(RegularGridMediatorTest, TestToolbarsNormalModeWithoutWebstates) {
+  EXPECT_EQ(3UL, consumer_.items.size());
+  [mediator_ saveAndCloseAllItems];
+  EXPECT_EQ(0UL, consumer_.items.size());
+
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.newTabButton);
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.searchButton);
+  EXPECT_TRUE(fake_toolbars_mediator_.configuration.undoButton);
+
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.closeAllButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.doneButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.selectTabsButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.deselectAllButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.selectAllButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.addToButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.closeSelectedTabsButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.shareButton);
+  EXPECT_FALSE(fake_toolbars_mediator_.configuration.cancelSearchButton);
+}
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_mediator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_mediator.mm
index 0adb566..e8ce8c6 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_mediator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_mediator.mm
@@ -6,7 +6,6 @@
 
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_buttons_delegate.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_toolbars_configuration.h"
-
 #import "ios/chrome/browser/ui/menu/action_factory.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_bottom_toolbar.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/tab_grid_top_toolbar.h"
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/BUILD.gn
new file mode 100644
index 0000000..6fdb6441
--- /dev/null
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/BUILD.gn
@@ -0,0 +1,20 @@
+# 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.
+
+source_set("fakes") {
+  testonly = true
+
+  sources = [
+    "fake_tab_grid_toolbars_mediator.h",
+    "fake_tab_grid_toolbars_mediator.mm",
+  ]
+
+  deps = [
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid:tab_grid_paging",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/grid:grid_toolbars_mutator",
+    "//ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars",
+  ]
+
+  frameworks = [ "Foundation.framework" ]
+}
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.h b/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.h
new file mode 100644
index 0000000..0daa39d
--- /dev/null
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.h
@@ -0,0 +1,26 @@
+// 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 IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_TOOLBARS_TEST_FAKE_TAB_GRID_TOOLBARS_MEDIATOR_H_
+#define IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_TOOLBARS_TEST_FAKE_TAB_GRID_TOOLBARS_MEDIATOR_H_
+
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_toolbars_mutator.h"
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_paging.h"
+
+@protocol GridToolbarsMutator;
+@protocol TabGridToolbarsButtonsDelegate;
+@protocol TabGridToolbarsConfiguration;
+
+// Fake mediator class that implement the mutator to be able to verify the
+// received value.
+@interface FakeTabGridToolbarsMediator : NSObject <GridToolbarsMutator>
+
+@property(nonatomic, strong) TabGridToolbarsConfiguration* configuration;
+@property(nonatomic, weak) id<TabGridToolbarsButtonsDelegate> delegate;
+@property(nonatomic, assign) TabGridMode mode;
+@property(nonatomic, assign) BOOL enabled;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_TAB_SWITCHER_TAB_GRID_TOOLBARS_TEST_FAKE_TAB_GRID_TOOLBARS_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.mm
new file mode 100644
index 0000000..ce5f1f12
--- /dev/null
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.mm
@@ -0,0 +1,26 @@
+// 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.
+
+#import "ios/chrome/browser/ui/tab_switcher/tab_grid/toolbars/test/fake_tab_grid_toolbars_mediator.h"
+
+@implementation FakeTabGridToolbarsMediator
+
+- (void)setToolbarConfiguration:(TabGridToolbarsConfiguration*)configuration {
+  self.configuration = configuration;
+}
+
+- (void)setToolbarsButtonsDelegate:
+    (id<TabGridToolbarsButtonsDelegate>)delegate {
+  self.delegate = delegate;
+}
+
+- (void)setToolbarsMode:(TabGridMode)mode {
+  self.mode = mode;
+}
+
+- (void)setButtonsEnabled:(BOOL)enabled {
+  self.enabled = enabled;
+}
+
+@end
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 80e23fe..0037590 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -210,7 +210,7 @@
     "//ios/chrome/browser/crash_report/model/breadcrumbs:unit_tests",
     "//ios/chrome/browser/default_browser/model:unit_tests",
     "//ios/chrome/browser/device_reauth:unit_tests",
-    "//ios/chrome/browser/device_sharing:unit_tests",
+    "//ios/chrome/browser/device_sharing/model:unit_tests",
     "//ios/chrome/browser/download/model:unit_tests",
     "//ios/chrome/browser/download/model/background_service:unit_tests",
     "//ios/chrome/browser/enterprise/model/idle:unit_tests",
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index d789264..3f13b44 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -85,7 +85,7 @@
     "//ios/chrome/browser/content_settings/model:model",
     "//ios/chrome/browser/default_browser/model:test_support",
     "//ios/chrome/browser/default_browser/model:utils",
-    "//ios/chrome/browser/device_sharing:eg_app_support+eg2",
+    "//ios/chrome/browser/device_sharing/model:eg_app_support+eg2",
     "//ios/chrome/browser/find_in_page/model",
     "//ios/chrome/browser/find_in_page/model:eg_app_support+eg2",
     "//ios/chrome/browser/first_run/model",
diff --git a/ios/chrome/test/earl_grey2/BUILD.gn b/ios/chrome/test/earl_grey2/BUILD.gn
index 51b01cc..710dcd4 100644
--- a/ios/chrome/test/earl_grey2/BUILD.gn
+++ b/ios/chrome/test/earl_grey2/BUILD.gn
@@ -81,7 +81,7 @@
 
   deps = [
     "//ios/chrome/browser/autofill:eg2_tests",
-    "//ios/chrome/browser/device_sharing:eg2_tests",
+    "//ios/chrome/browser/device_sharing/model:eg2_tests",
     "//ios/chrome/browser/feature_engagement/model:eg2_tests",
     "//ios/chrome/browser/find_in_page/model:eg2_tests",
     "//ios/chrome/browser/https_upgrades/model:eg2_tests",
diff --git a/ios/web/annotations/annotations_java_script_feature.mm b/ios/web/annotations/annotations_java_script_feature.mm
index 398a2a7d..cc333d5 100644
--- a/ios/web/annotations/annotations_java_script_feature.mm
+++ b/ios/web/annotations/annotations_java_script_feature.mm
@@ -7,6 +7,7 @@
 #import <vector>
 
 #import "base/logging.h"
+#import "base/metrics/histogram_macros.h"
 #import "base/no_destructor.h"
 #import "base/strings/sys_string_conversions.h"
 #import "components/shared_highlighting/ios/parsing_utils.h"
@@ -160,12 +161,16 @@
     std::optional<CGRect> rect =
         shared_highlighting::ParseRect(dict.FindDict("rect"));
     const std::string* text = dict.FindString("text");
-    if (!data || !rect || !text) {
+    std::optional<bool> cancel = dict.FindBool("cancel");
+    if (!data || !rect || !text || !cancel) {
       return;
     }
-    manager->OnClick(
-        web_state, *text,
-        shared_highlighting::ConvertToBrowserRect(*rect, web_state), *data);
+    UMA_HISTOGRAM_BOOLEAN("IOS.Annotations.UserTap.Cancelled", *cancel);
+    if (!*cancel) {
+      manager->OnClick(
+          web_state, *text,
+          shared_highlighting::ConvertToBrowserRect(*rect, web_state), *data);
+    }
   }
 }
 
diff --git a/ios/web/annotations/resources/annotations.ts b/ios/web/annotations/resources/annotations.ts
index b7796dc..317f5fe 100644
--- a/ios/web/annotations/resources/annotations.ts
+++ b/ios/web/annotations/resources/annotations.ts
@@ -62,7 +62,7 @@
  * `stopObserving`.
  */
 class MutationsDuringClickTracker {
-  mutationCount = 0;
+  hasMutations = false;
   mutationObserver: MutationObserver;
   mutationExtendId = 0;
 
@@ -71,7 +71,15 @@
   constructor(private readonly initialEvent: Event) {
     this.mutationObserver =
         new MutationObserver((mutationList: MutationRecord[]) => {
-          this.mutationCount += mutationList.length;
+          if (this.hasMutations) {
+            return;
+          }
+          for (let mutation of mutationList) {
+            if (mutation.target.contains(this.initialEvent.target as Node)) {
+              this.hasMutations = true;
+              break;
+            }
+          }
         });
     this.mutationObserver.observe(
         document, {attributes: false, childList: true, subtree: true});
@@ -81,12 +89,7 @@
   // or it was prevented or if any DOM mutations occurred.
   hasPreventativeActivity(event: Event): boolean {
     return event !== this.initialEvent || event.defaultPrevented ||
-        this.hasMutations();
-  }
-
-  // Returns true if DOM mutations occurred.
-  hasMutations(): boolean {
-    return this.mutationCount > 0;
+        this.hasMutations;
   }
 
   // Extends DOM observation by triggering `then` after de delay. This can be
@@ -549,23 +552,27 @@
       !mutationDuringClickObserver.hasPreventativeActivity(event)) {
     const annotation = event.target;
     mutationDuringClickObserver.extendObservation(() => {
-      if (mutationDuringClickObserver &&
-          !mutationDuringClickObserver.hasMutations()) {
+      if (mutationDuringClickObserver) {
         highlightAnnotation(annotation);
-        sendWebKitMessage('annotations', {
-          command: 'annotations.onClick',
-          data: annotation.dataset['data'],
-          rect: rectFromElement(annotation),
-          text: annotation.dataset['annotation'],
-        });
-        cancelObserver();
+        onClickAnnotation(annotation, mutationDuringClickObserver.hasMutations);
       }
     });
   } else {
-    cancelObserver();
+    onClickAnnotation(event.target as HTMLElement, true);
   }
 }
 
+function onClickAnnotation(annotation: HTMLElement, cancel: boolean): void {
+  sendWebKitMessage('annotations', {
+    command: 'annotations.onClick',
+    cancel: cancel,
+    data: annotation.dataset['data'],
+    rect: rectFromElement(annotation),
+    text: annotation.dataset['annotation'],
+  });
+  cancelObserver();
+}
+
 /**
  * Highlights all elements related to the annotation of which `annotation` is an
  * element of.
diff --git a/ios_internal b/ios_internal
index 26cadba..ffad5e3 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 26cadba9564d99a7fe8e13433f80c37e6e1d3ed7
+Subproject commit ffad5e3cdc70181d9913574cd90ede7b9be515da
diff --git a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.h b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.h
index f2b593f3..8b824be 100644
--- a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.h
+++ b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/memory/raw_ptr.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread.h"
@@ -97,7 +98,7 @@
   scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
 
   // The client of this class.
-  Client* client_ = nullptr;
+  raw_ptr<Client> client_ = nullptr;
 
   // The task runner on which the functions of |encoder_| are executed.
   scoped_refptr<base::SequencedTaskRunner> encoder_task_runner_;
diff --git a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
index 2f54c51e..1e88da01 100644
--- a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
@@ -547,7 +547,6 @@
 VaapiMjpegDecodeAccelerator::VaapiMjpegDecodeAccelerator(
     const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
     : io_task_runner_(io_task_runner),
-      client_(nullptr),
       weak_this_factory_(this) {
   DCHECK(io_task_runner_->BelongsToCurrentThread());
 }
diff --git a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h
index a6c275f..c99f3fc0 100644
--- a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h
+++ b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h
@@ -10,6 +10,7 @@
 #include <memory>
 
 #include "base/containers/span.h"
+#include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/memory/weak_ptr.h"
@@ -76,7 +77,7 @@
   const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
 
   // The client of this class.
-  chromeos_camera::MjpegDecodeAccelerator::Client* client_;
+  raw_ptr<chromeos_camera::MjpegDecodeAccelerator::Client> client_ = nullptr;
 
   std::unique_ptr<Decoder> decoder_;
 
diff --git a/media/gpu/vaapi/vaapi_video_decoder_delegate.h b/media/gpu/vaapi/vaapi_video_decoder_delegate.h
index 4177e4c0..90e58bd5 100644
--- a/media/gpu/vaapi/vaapi_video_decoder_delegate.h
+++ b/media/gpu/vaapi/vaapi_video_decoder_delegate.h
@@ -149,7 +149,8 @@
   ProtectedSessionUpdateCB on_protected_session_update_cb_;
   EncryptionScheme encryption_scheme_;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  chromeos::ChromeOsCdmContext* chromeos_cdm_context_{nullptr};  // Not owned.
+  // Not owned.
+  raw_ptr<chromeos::ChromeOsCdmContext> chromeos_cdm_context_ = nullptr;
   EncryptionScheme last_used_encryption_scheme_{EncryptionScheme::kUnencrypted};
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   ProtectedSessionState protected_session_state_;
diff --git a/net/base/filename_util.cc b/net/base/filename_util.cc
index 7c126b9..f16d5dc 100644
--- a/net/base/filename_util.cc
+++ b/net/base/filename_util.cc
@@ -197,12 +197,10 @@
 #endif
 
   for (const char* const device : known_devices) {
-    // Exact match.
-    if (filename_lower == device)
-      return true;
-    // Starts with "DEVICE.".
-    if (base::StartsWith(filename_lower, std::string(device) + ".",
-                         base::CompareCase::SENSITIVE)) {
+    // Check for an exact match, or a "DEVICE." prefix.
+    size_t len = strlen(device);
+    if (filename_lower.starts_with(device) &&
+        (filename_lower.size() == len || filename_lower[len] == '.')) {
       return true;
     }
   }
diff --git a/net/base/hash_value.cc b/net/base/hash_value.cc
index cbee5270..9518685 100644
--- a/net/base/hash_value.cc
+++ b/net/base/hash_value.cc
@@ -47,7 +47,7 @@
 }
 
 bool HashValue::FromString(std::string_view value) {
-  if (!base::StartsWith(value, kSha256Slash)) {
+  if (!value.starts_with(kSha256Slash)) {
     return false;
   }
 
diff --git a/net/base/mime_sniffer.cc b/net/base/mime_sniffer.cc
index 1f7667e..c1c8968 100644
--- a/net/base/mime_sniffer.cc
+++ b/net/base/mime_sniffer.cc
@@ -648,8 +648,9 @@
       MAGIC_NUMBER("application/x-chrome-extension", "Cr24\x03\x00\x00\x00")};
 
   // Only consider files that have the extension ".crx".
-  if (!base::EndsWith(url.path_piece(), ".crx", base::CompareCase::SENSITIVE))
+  if (!url.path_piece().ends_with(".crx")) {
     return false;
+  }
 
   *have_enough_content &= TruncateStringPiece(kBytesRequiredForMagic, &content);
   return CheckForMagicNumbers(content, kCRXMagicNumbers, result);
diff --git a/net/base/network_interfaces_linux.cc b/net/base/network_interfaces_linux.cc
index c192c33..4960b35 100644
--- a/net/base/network_interfaces_linux.cc
+++ b/net/base/network_interfaces_linux.cc
@@ -235,7 +235,7 @@
     // See https://crbug.com/1240237 for more context.
     bool use_alternative_getifaddrs =
         std::string_view(build_info->brand()) == "samsung" &&
-        base::StartsWith(build_info->hardware(), "mt");
+        std::string_view(build_info->hardware()).starts_with("mt");
     bool ret = internal::GetNetworkListUsingGetifaddrs(
         networks, policy, use_alternative_getifaddrs);
     // Use GetInterfaceConnectionType() to sharpen up interface types.
diff --git a/net/base/prioritized_task_runner_unittest.cc b/net/base/prioritized_task_runner_unittest.cc
index ee61013..138e053 100644
--- a/net/base/prioritized_task_runner_unittest.cc
+++ b/net/base/prioritized_task_runner_unittest.cc
@@ -49,8 +49,9 @@
   std::vector<std::string> TaskOrder() {
     std::vector<std::string> out;
     for (const std::string& name : callback_names_) {
-      if (base::StartsWith(name, "Task", base::CompareCase::SENSITIVE))
+      if (name.starts_with("Task")) {
         out.push_back(name);
+      }
     }
     return out;
   }
@@ -58,8 +59,9 @@
   std::vector<std::string> ReplyOrder() {
     std::vector<std::string> out;
     for (const std::string& name : callback_names_) {
-      if (base::StartsWith(name, "Reply", base::CompareCase::SENSITIVE))
+      if (name.starts_with("Reply")) {
         out.push_back(name);
+      }
     }
     return out;
   }
diff --git a/net/base/scheme_host_port_matcher_rule.cc b/net/base/scheme_host_port_matcher_rule.cc
index e789dab..4c6a94b 100644
--- a/net/base/scheme_host_port_matcher_rule.cc
+++ b/net/base/scheme_host_port_matcher_rule.cc
@@ -90,7 +90,7 @@
   // Special-case hostnames that begin with a period.
   // For example, we remap ".google.com" --> "*.google.com".
   std::string hostname_pattern;
-  if (base::StartsWith(raw, ".", base::CompareCase::SENSITIVE)) {
+  if (raw.starts_with(".")) {
     hostname_pattern = base::StrCat({"*", raw});
   } else {
     hostname_pattern = std::string(raw);
@@ -157,7 +157,7 @@
 
 std::unique_ptr<SchemeHostPortMatcherHostnamePatternRule>
 SchemeHostPortMatcherHostnamePatternRule::GenerateSuffixMatchingRule() const {
-  if (!base::StartsWith(hostname_pattern_, "*", base::CompareCase::SENSITIVE)) {
+  if (!hostname_pattern_.starts_with("*")) {
     return std::make_unique<SchemeHostPortMatcherHostnamePatternRule>(
         optional_scheme_, "*" + hostname_pattern_, optional_port_);
   }
diff --git a/net/base/url_util.cc b/net/base/url_util.cc
index 3b5199f7..81e7c3f 100644
--- a/net/base/url_util.cc
+++ b/net/base/url_util.cc
@@ -309,8 +309,9 @@
 
   // Superdomain must be suffix of subdomain, and the last character not
   // included in the matching substring must be a dot.
-  if (!base::EndsWith(subdomain, superdomain))
+  if (!subdomain.ends_with(superdomain)) {
     return false;
+  }
   subdomain.remove_suffix(superdomain.length());
   return subdomain.back() == '.';
 }
@@ -508,8 +509,9 @@
     // Here it's possible to get away with faster case-sensitive comparisons
     // because the list above is all lowercase, and a GURL's host name will
     // always be canonicalized to lowercase as well.
-    if (base::EndsWith(host, suffix))
+    if (host.ends_with(suffix)) {
       return true;
+    }
   }
   return false;
 }
diff --git a/net/cert/x509_certificate.cc b/net/cert/x509_certificate.cc
index b3c4f68..a99c6ce 100644
--- a/net/cert/x509_certificate.cc
+++ b/net/cert/x509_certificate.cc
@@ -495,7 +495,7 @@
   SplitOnChar(reference_name, '.', &reference_host, &reference_domain);
   bool allow_wildcards = false;
   if (!reference_domain.empty()) {
-    DCHECK(base::StartsWith(reference_domain, "."));
+    DCHECK(reference_domain.starts_with("."));
 
     // Do not allow wildcards for public/ICANN registry controlled domains -
     // that is, prevent *.com or *.co.uk as valid presented names, but do not
diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc
index b81e165..c5f2b7d 100644
--- a/net/cookies/canonical_cookie.cc
+++ b/net/cookies/canonical_cookie.cc
@@ -706,7 +706,7 @@
   // Check for "__" prefixed names, excluding the cookie prefixes.
   bool name_prefixed_with_underscores =
       (prefix_case_insensitive == CanonicalCookie::COOKIE_PREFIX_NONE) &&
-      base::StartsWith(parsed_cookie.Name(), "__");
+      parsed_cookie.Name().starts_with("__");
 
   UMA_HISTOGRAM_BOOLEAN("Cookie.DoubleUnderscorePrefixedName",
                         name_prefixed_with_underscores);
diff --git a/net/cookies/cookie_util.cc b/net/cookies/cookie_util.cc
index b479ab1..89ecc0f6 100644
--- a/net/cookies/cookie_util.cc
+++ b/net/cookies/cookie_util.cc
@@ -342,7 +342,7 @@
   // a sequence of individual domain name labels"; a label can only be empty if
   // it is the last label in the name, but a name ending in `..` would have an
   // empty label in the penultimate position and is thus invalid.
-  if (base::EndsWith(url_host, "..")) {
+  if (url_host.ends_with("..")) {
     return false;
   }
   // If no domain was specified in the domain string, default to a host cookie.
@@ -622,7 +622,7 @@
 
   // Make sure the cookie path is a prefix of the url path.  If the url path is
   // shorter than the cookie path, then the cookie path can't be a prefix.
-  if (!base::StartsWith(url_path, cookie_path, base::CompareCase::SENSITIVE)) {
+  if (!url_path.starts_with(cookie_path)) {
     return false;
   }
 
diff --git a/net/disk_cache/simple/simple_file_tracker_unittest.cc b/net/disk_cache/simple/simple_file_tracker_unittest.cc
index 719d217a..8be29300 100644
--- a/net/disk_cache/simple/simple_file_tracker_unittest.cc
+++ b/net/disk_cache/simple/simple_file_tracker_unittest.cc
@@ -348,8 +348,7 @@
   UpdateEntryFileKey(entries[1].get(), key);
   base::FilePath new_path =
       entries[1]->GetFilenameForSubfile(SimpleFileTracker::SubFile::FILE_0);
-  EXPECT_TRUE(base::StartsWith(new_path.BaseName().MaybeAsASCII(), "todelete_",
-                               base::CompareCase::SENSITIVE));
+  EXPECT_TRUE(new_path.BaseName().MaybeAsASCII().starts_with("todelete_"));
   EXPECT_TRUE(base::Move(old_path, new_path));
 
   // Now re-acquire everything again; this time reading.
diff --git a/net/disk_cache/simple/simple_index_file.cc b/net/disk_cache/simple/simple_index_file.cc
index a30bce7..4e6c698 100644
--- a/net/disk_cache/simple/simple_index_file.cc
+++ b/net/disk_cache/simple/simple_index_file.cc
@@ -142,7 +142,7 @@
   const std::string file_name(base_name.begin(), base_name.end());
 
   // Cleanup any left over doomed entries.
-  if (base::StartsWith(file_name, "todelete_", base::CompareCase::SENSITIVE)) {
+  if (file_name.starts_with("todelete_")) {
     file_operations->DeleteFile(file_path);
     return;
   }
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc
index 6b1dc26..04e606a7 100644
--- a/net/dns/host_resolver_manager.cc
+++ b/net/dns/host_resolver_manager.cc
@@ -187,8 +187,7 @@
 
 // True if |hostname| ends with either ".local" or ".local.".
 bool ResemblesMulticastDNSName(base::StringPiece hostname) {
-  return base::EndsWith(hostname, ".local") ||
-         base::EndsWith(hostname, ".local.");
+  return hostname.ends_with(".local") || hostname.ends_with(".local.");
 }
 
 bool ConfigureAsyncDnsNoFallbackFieldTrial() {
diff --git a/net/dns/mdns_client_impl.cc b/net/dns/mdns_client_impl.cc
index 5f400d1..0a1690ab 100644
--- a/net/dns/mdns_client_impl.cc
+++ b/net/dns/mdns_client_impl.cc
@@ -68,11 +68,11 @@
       "_uscan._tcp.local",
   });
 
-  if (base::EndsWith(host, "_googlecast._tcp.local")) {
+  if (host.ends_with("_googlecast._tcp.local")) {
     base::UmaHistogramEnumeration("Network.Mdns.Googlecast", query_type);
   } else if (base::ranges::any_of(kPrintScanServices,
                                   [&host](std::string_view service) {
-                                    return base::EndsWith(host, service);
+                                    return host.ends_with(service);
                                   })) {
     base::UmaHistogramEnumeration("Network.Mdns.PrintScan", query_type);
   } else {
diff --git a/net/dns/nsswitch_reader.cc b/net/dns/nsswitch_reader.cc
index dfe06799..e9966815 100644
--- a/net/dns/nsswitch_reader.cc
+++ b/net/dns/nsswitch_reader.cc
@@ -66,16 +66,16 @@
                                base::StringPiece database_name) {
   DCHECK(!text.empty());
   DCHECK(!database_name.empty());
-  DCHECK(!base::StartsWith(database_name, "#"));
+  DCHECK(!database_name.starts_with("#"));
   DCHECK(!base::IsAsciiWhitespace(database_name.front()));
-  DCHECK(base::EndsWith(database_name, ":"));
+  DCHECK(database_name.ends_with(":"));
 
   while (!text.empty()) {
     text = base::TrimWhitespaceASCII(text, base::TrimPositions::TRIM_LEADING);
 
     if (base::StartsWith(text, database_name,
                          base::CompareCase::INSENSITIVE_ASCII)) {
-      DCHECK(!base::StartsWith(text, "#"));
+      DCHECK(!text.starts_with("#"));
 
       text = text.substr(database_name.size());
       base::StringPiece::size_type line_end = text.find('\n');
diff --git a/net/http/http_auth_cache.cc b/net/http/http_auth_cache.cc
index d81c0b07..4a23daa 100644
--- a/net/http/http_auth_cache.cc
+++ b/net/http/http_auth_cache.cc
@@ -36,8 +36,7 @@
 bool IsEnclosingPath(const std::string& container, const std::string& path) {
   DCHECK(container.empty() || *(container.end() - 1) == '/');
   return ((container.empty() && path.empty()) ||
-          (!container.empty() &&
-           base::StartsWith(path, container, base::CompareCase::SENSITIVE)));
+          (!container.empty() && path.starts_with(container)));
 }
 
 #if DCHECK_IS_ON()
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index e95c661..8b5581c 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -3668,8 +3668,7 @@
         CACHE_STATUS_HISTOGRAMS(".CSSThirdParty");
       }
       CACHE_STATUS_HISTOGRAMS(".CSS");
-    } else if (base::StartsWith(mime_type, "image/",
-                                base::CompareCase::SENSITIVE)) {
+    } else if (mime_type.starts_with("image/")) {
       int64_t content_length = response_headers->GetContentLength();
       if (content_length >= 0 && content_length < 100) {
         CACHE_STATUS_HISTOGRAMS(".TinyImage");
@@ -3677,10 +3676,8 @@
         CACHE_STATUS_HISTOGRAMS(".NonTinyImage");
       }
       CACHE_STATUS_HISTOGRAMS(".Image");
-    } else if (base::EndsWith(mime_type, "javascript",
-                              base::CompareCase::SENSITIVE) ||
-               base::EndsWith(mime_type, "ecmascript",
-                              base::CompareCase::SENSITIVE)) {
+    } else if (mime_type.ends_with("javascript") ||
+               mime_type.ends_with("ecmascript")) {
       if (is_third_party) {
         CACHE_STATUS_HISTOGRAMS(".JavaScriptThirdParty");
       }
@@ -3690,11 +3687,9 @@
         CACHE_STATUS_HISTOGRAMS(".FontThirdParty");
       }
       CACHE_STATUS_HISTOGRAMS(".Font");
-    } else if (base::StartsWith(mime_type, "audio/",
-                                base::CompareCase::SENSITIVE)) {
+    } else if (mime_type.starts_with("audio/")) {
       CACHE_STATUS_HISTOGRAMS(".Audio");
-    } else if (base::StartsWith(mime_type, "video/",
-                                base::CompareCase::SENSITIVE)) {
+    } else if (mime_type.starts_with("video/")) {
       CACHE_STATUS_HISTOGRAMS(".Video");
     }
   }
diff --git a/net/http/http_util.cc b/net/http/http_util.cc
index f8c216c..a1e6a4e6 100644
--- a/net/http/http_util.cc
+++ b/net/http/http_util.cc
@@ -1103,7 +1103,7 @@
     if (qvalue.empty())
       return false;
     if (qvalue[0] == '1') {
-      if (base::StartsWith("1.000", qvalue)) {
+      if (std::string_view("1.000").starts_with(qvalue)) {
         allowed_encodings->insert(base::ToLowerASCII(encoding));
         continue;
       }
diff --git a/net/proxy_resolution/pac_file_fetcher_impl.cc b/net/proxy_resolution/pac_file_fetcher_impl.cc
index e13fb8d..2b6659d 100644
--- a/net/proxy_resolution/pac_file_fetcher_impl.cc
+++ b/net/proxy_resolution/pac_file_fetcher_impl.cc
@@ -81,7 +81,7 @@
     // Guess the charset by looking at the BOM.
     base::StringPiece bytes_str(bytes);
     for (const auto& bom : kBomMappings) {
-      if (base::StartsWith(bytes_str, bom.prefix)) {
+      if (bytes_str.starts_with(bom.prefix)) {
         return ConvertResponseToUTF16(
             bom.charset,
             // Strip the BOM in the converted response.
diff --git a/net/proxy_resolution/proxy_bypass_rules.cc b/net/proxy_resolution/proxy_bypass_rules.cc
index dd15d15..74a188e 100644
--- a/net/proxy_resolution/proxy_bypass_rules.cc
+++ b/net/proxy_resolution/proxy_bypass_rules.cc
@@ -34,8 +34,8 @@
 bool IsLinkLocalIP(const GURL& url) {
   // Quick fail if definitely not link-local, to avoid doing unnecessary work in
   // common case.
-  if (!(base::StartsWith(url.host_piece(), "169.254.") ||
-        base::StartsWith(url.host_piece(), "["))) {
+  if (!(url.host_piece().starts_with("169.254.") ||
+        url.host_piece().starts_with("["))) {
     return false;
   }
 
@@ -53,8 +53,9 @@
 // addresses. However for proxy resolving such URLs should bypass the use
 // of a PAC script, since the destination is local.
 bool IsIPv4MappedLoopback(const GURL& url) {
-  if (!base::StartsWith(url.host_piece(), "[::ffff"))
+  if (!url.host_piece().starts_with("[::ffff")) {
     return false;
+  }
 
   IPAddress ip_address;
   if (!ip_address.AssignFromIPLiteral(url.HostNoBracketsPiece()))
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
index 48f734df..9fb77301 100644
--- a/net/quic/quic_http_stream_test.cc
+++ b/net/quic/quic_http_stream_test.cc
@@ -143,7 +143,7 @@
     if (!header) {
       return false;
     }
-    if (base::StartsWith(*header, header_prefix)) {
+    if (header->starts_with(header_prefix)) {
       if (header_found) {
         return false;
       }
diff --git a/net/server/http_server_unittest.cc b/net/server/http_server_unittest.cc
index 3cce905..8b4aa00 100644
--- a/net/server/http_server_unittest.cc
+++ b/net/server/http_server_unittest.cc
@@ -350,8 +350,7 @@
   ASSERT_EQ("/test", request.info.path);
   ASSERT_EQ("", request.info.data);
   ASSERT_EQ(0u, request.info.headers.size());
-  ASSERT_TRUE(base::StartsWith(request.info.peer.ToString(), "127.0.0.1",
-                               base::CompareCase::SENSITIVE));
+  ASSERT_TRUE(request.info.peer.ToString().starts_with("127.0.0.1"));
 }
 
 TEST_F(HttpServerTest, RequestBrokenTermination) {
@@ -899,10 +898,8 @@
 
   std::string response;
   ASSERT_TRUE(client.ReadResponse(&response));
-  ASSERT_TRUE(base::StartsWith(response, "HTTP/1.1 200 OK",
-                               base::CompareCase::SENSITIVE));
-  ASSERT_TRUE(
-      base::EndsWith(response, "Response!", base::CompareCase::SENSITIVE));
+  ASSERT_TRUE(response.starts_with("HTTP/1.1 200 OK"));
+  ASSERT_TRUE(response.ends_with("Response!"));
 }
 
 TEST_F(HttpServerTest, SendRaw) {
@@ -1076,10 +1073,8 @@
                    TRAFFIC_ANNOTATION_FOR_TESTS);
   std::string response1;
   ASSERT_TRUE(client.ReadResponse(&response1));
-  ASSERT_TRUE(base::StartsWith(response1, "HTTP/1.1 200 OK",
-                               base::CompareCase::SENSITIVE));
-  ASSERT_TRUE(base::EndsWith(response1, "Content for /test",
-                             base::CompareCase::SENSITIVE));
+  ASSERT_TRUE(response1.starts_with("HTTP/1.1 200 OK"));
+  ASSERT_TRUE(response1.ends_with("Content for /test"));
 
   client.Send("GET /test2 HTTP/1.1\r\n\r\n");
   auto second_request = WaitForRequest();
@@ -1089,8 +1084,7 @@
   server_->Send404(client_connection_id, TRAFFIC_ANNOTATION_FOR_TESTS);
   std::string response2;
   ASSERT_TRUE(client.ReadResponse(&response2));
-  ASSERT_TRUE(base::StartsWith(response2, "HTTP/1.1 404 Not Found",
-                               base::CompareCase::SENSITIVE));
+  ASSERT_TRUE(response2.starts_with("HTTP/1.1 404 Not Found"));
 
   client.Send("GET /test3 HTTP/1.1\r\n\r\n");
   auto third_request = WaitForRequest();
@@ -1101,10 +1095,8 @@
                    TRAFFIC_ANNOTATION_FOR_TESTS);
   std::string response3;
   ASSERT_TRUE(client.ReadResponse(&response3));
-  ASSERT_TRUE(base::StartsWith(response3, "HTTP/1.1 200 OK",
-                               base::CompareCase::SENSITIVE));
-  ASSERT_TRUE(base::EndsWith(response3, "Content for /test3",
-                             base::CompareCase::SENSITIVE));
+  ASSERT_TRUE(response3.starts_with("HTTP/1.1 200 OK"));
+  ASSERT_TRUE(response3.ends_with("Content for /test3"));
 }
 
 class CloseOnConnectHttpServerTest : public HttpServerTest {
diff --git a/net/spdy/spdy_session_test_util.cc b/net/spdy/spdy_session_test_util.cc
index 00d067d..5ae562b 100644
--- a/net/spdy/spdy_session_test_util.cc
+++ b/net/spdy/spdy_session_test_util.cc
@@ -4,8 +4,9 @@
 
 #include "net/spdy/spdy_session_test_util.h"
 
+#include <string_view>
+
 #include "base/location.h"
-#include "base/strings/string_util.h"
 #include "base/task/current_thread.h"
 
 namespace net {
@@ -27,10 +28,10 @@
 
 void SpdySessionTestTaskObserver::DidProcessTask(
     const base::PendingTask& pending_task) {
-  if (base::EndsWith(pending_task.posted_from.file_name(), file_name_,
-                     base::CompareCase::SENSITIVE) &&
-      base::EndsWith(pending_task.posted_from.function_name(), function_name_,
-                     base::CompareCase::SENSITIVE)) {
+  if (std::string_view(pending_task.posted_from.file_name())
+          .ends_with(file_name_) &&
+      std::string_view(pending_task.posted_from.function_name())
+          .ends_with(function_name_)) {
     ++executed_count_;
   }
 }
diff --git a/net/test/embedded_test_server/controllable_http_response.cc b/net/test/embedded_test_server/controllable_http_response.cc
index 7a3246f..c95b76b 100644
--- a/net/test/embedded_test_server/controllable_http_response.cc
+++ b/net/test/embedded_test_server/controllable_http_response.cc
@@ -146,8 +146,7 @@
 
   if (request.relative_url == relative_url ||
       (relative_url_is_prefix &&
-       base::StartsWith(request.relative_url, relative_url,
-                        base::CompareCase::SENSITIVE))) {
+       request.relative_url.starts_with(relative_url))) {
     *available = false;
     return std::make_unique<ControllableHttpResponse::Interceptor>(
         controller, controller_task_runner, request);
diff --git a/net/test/embedded_test_server/default_handlers.cc b/net/test/embedded_test_server/default_handlers.cc
index 8907b50c..e897b29 100644
--- a/net/test/embedded_test_server/default_handlers.cc
+++ b/net/test/embedded_test_server/default_handlers.cc
@@ -221,8 +221,7 @@
   http_response->set_content_type("text/html");
   http_response->set_content(body);
 
-  if (base::EndsWith(request.GetURL().path_piece(), "/nocache",
-                     base::CompareCase::SENSITIVE)) {
+  if (request.GetURL().path_piece().ends_with("/nocache")) {
     http_response->AddCustomHeader("Cache-Control",
                                    "no-cache, no-store, must-revalidate");
   }
diff --git a/net/test/embedded_test_server/embedded_test_server.cc b/net/test/embedded_test_server/embedded_test_server.cc
index da1ce3f3..80cbd97 100644
--- a/net/test/embedded_test_server/embedded_test_server.cc
+++ b/net/test/embedded_test_server/embedded_test_server.cc
@@ -85,8 +85,7 @@
     const std::string& content,
     const HttpRequest& request) {
   if (request.GetURL().path() != expected_path &&
-      !base::StartsWith(request.GetURL().path(), expected_path + "/",
-                        base::CompareCase::SENSITIVE)) {
+      !request.GetURL().path().starts_with(expected_path + "/")) {
     return nullptr;
   }
 
@@ -697,8 +696,7 @@
 
 GURL EmbeddedTestServer::GetURL(base::StringPiece relative_url) const {
   DCHECK(Started()) << "You must start the server first.";
-  DCHECK(base::StartsWith(relative_url, "/", base::CompareCase::SENSITIVE))
-      << relative_url;
+  DCHECK(relative_url.starts_with("/")) << relative_url;
   return base_url_.Resolve(relative_url);
 }
 
diff --git a/net/test/embedded_test_server/request_handler_util.cc b/net/test/embedded_test_server/request_handler_util.cc
index fa3434b4..a865052 100644
--- a/net/test/embedded_test_server/request_handler_util.cc
+++ b/net/test/embedded_test_server/request_handler_util.cc
@@ -84,9 +84,7 @@
   }
 
   GURL url = request.GetURL();
-  return url.path() == path_prefix ||
-         base::StartsWith(url.path(), path_prefix + "/",
-                          base::CompareCase::SENSITIVE);
+  return url.path() == path_prefix || url.path().starts_with(path_prefix + "/");
 }
 
 std::unique_ptr<HttpResponse> HandlePrefixedRequest(
@@ -168,9 +166,8 @@
   GURL request_url = request.GetURL();
   std::string relative_path(request_url.path());
 
-  std::string post_prefix("/post/");
-  if (base::StartsWith(relative_path, post_prefix,
-                       base::CompareCase::SENSITIVE)) {
+  std::string_view post_prefix("/post/");
+  if (relative_path.starts_with(post_prefix)) {
     if (request.method != METHOD_POST)
       return nullptr;
     relative_path = relative_path.substr(post_prefix.size() - 1);
@@ -202,7 +199,7 @@
   }
 
   // Trim the first byte ('/').
-  DCHECK(base::StartsWith(relative_path, "/", base::CompareCase::SENSITIVE));
+  DCHECK(relative_path.starts_with("/"));
   std::string request_path = relative_path.substr(1);
   base::FilePath file_path(server_root.AppendASCII(request_path));
   std::string file_contents;
diff --git a/net/test/revocation_builder.cc b/net/test/revocation_builder.cc
index b2098549..edad94c 100644
--- a/net/test/revocation_builder.cc
+++ b/net/test/revocation_builder.cc
@@ -91,7 +91,7 @@
   // ExtractSubjectPublicKeyFromSPKI() includes the unused bit count. For this
   // application, the unused bit count must be zero, and is not included in the
   // result.
-  if (!base::StartsWith(spk, "\0")) {
+  if (spk.empty() || spk[0] != '\0') {
     ADD_FAILURE();
     return std::string();
   }
diff --git a/net/tools/tld_cleanup/tld_cleanup_util.cc b/net/tools/tld_cleanup/tld_cleanup_util.cc
index 6174d49..996a883 100644
--- a/net/tools/tld_cleanup/tld_cleanup_util.cc
+++ b/net/tools/tld_cleanup/tld_cleanup_util.cc
@@ -74,16 +74,18 @@
   NormalizeResult result = NormalizeResult::kSuccess;
 
   // Strip single leading and trailing dots.
-  if (base::StartsWith(domain, "."))
+  if (domain.starts_with(".")) {
     domain.erase(0, 1);
-  if (base::EndsWith(domain, "."))
+  }
+  if (domain.ends_with(".")) {
     domain.pop_back();
+  }
 
   // Allow single leading '*.' or '!', saved here so it's not canonicalized.
-  if (base::StartsWith(domain, "!")) {
+  if (domain.starts_with("!")) {
     domain.erase(0, 1);
     rule.exception = true;
-  } else if (base::StartsWith(domain, "*.")) {
+  } else if (domain.starts_with("*.")) {
     domain.erase(0, 2);
     rule.wildcard = true;
   }
@@ -126,15 +128,15 @@
   RuleMap extra_rules;
 
   for (std::string line; std::getline(data_stream, line, '\n');) {
-    if (base::StartsWith(line, kBeginPrivateDomainsComment)) {
+    if (line.starts_with(kBeginPrivateDomainsComment)) {
       in_private_section = true;
       continue;
     }
-    if (base::StartsWith(line, kEndPrivateDomainsComment)) {
+    if (line.starts_with(kEndPrivateDomainsComment)) {
       in_private_section = false;
       continue;
     }
-    if (base::StartsWith(line, "//")) {
+    if (line.starts_with("//")) {
       // Skip comments.
       continue;
     }
diff --git a/net/tools/transport_security_state_generator/input_file_parsers.cc b/net/tools/transport_security_state_generator/input_file_parsers.cc
index e61a052..2236115 100644
--- a/net/tools/transport_security_state_generator/input_file_parsers.cc
+++ b/net/tools/transport_security_state_generator/input_file_parsers.cc
@@ -65,11 +65,11 @@
   }
   base::StringPiece first_word = words[0];
 
-  if (base::EndsWith(first_word, ",")) {
+  if (first_word.ends_with(",")) {
     first_word = first_word.substr(0, first_word.size() - 1);
   }
 
-  if (base::StartsWith(first_word, "*.")) {
+  if (first_word.starts_with("*.")) {
     first_word = first_word.substr(2, first_word.size() - 2);
   }
 
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 903bb64af..ab99864 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -218,6 +218,36 @@
   return initiator == request_site;
 }
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class HttpRequestStsState {
+  kUnknown = 0,
+  kUnprotectedHttps = 1,
+  kProtectedHttps = 2,
+  kUnprotectedHttp = 3,
+  kProtectedHttp = 4,
+  kMaxValue = kProtectedHttp,
+};
+
+void RecordSTSHistogram(bool sts_enabled, bool is_secure, int load_flags) {
+  // Embrace the layering violation and only record the histogram for main frame
+  // navigations. It's possible to record this outside of net/, but the code is
+  // a lot more complicated, and while this flag is deprecated, there are no
+  // current plans to remove it. See crbug.com/516499 .
+  if (!(load_flags & net::LOAD_MAIN_FRAME_DEPRECATED)) {
+    return;
+  }
+  HttpRequestStsState sts_state = HttpRequestStsState::kUnknown;
+  if (is_secure) {
+    sts_state = (sts_enabled ? HttpRequestStsState::kProtectedHttps
+                             : HttpRequestStsState::kUnprotectedHttps);
+  } else {
+    sts_state = (sts_enabled ? HttpRequestStsState::kProtectedHttp
+                             : HttpRequestStsState::kUnprotectedHttp);
+  }
+  UMA_HISTOGRAM_ENUMERATION("Net.HttpRequestStsState", sts_state);
+}
+
 }  // namespace
 
 namespace net {
@@ -229,6 +259,10 @@
   DCHECK(request->context()->http_transaction_factory());
   DCHECK(url.SchemeIsHTTPOrHTTPS() || url.SchemeIsWSOrWSS());
 
+  TransportSecurityState* hsts = request->context()->transport_security_state();
+  bool should_upgrade_to_ssl =
+      hsts && hsts->ShouldUpgradeToSSL(url.host(), request->net_log());
+
   // Check for reasons not to return a URLRequestHttpJob. These don't apply to
   // https and wss requests.
   if (!url.SchemeIsCryptographic()) {
@@ -241,9 +275,9 @@
       CHECK(request->allow_credentials() == false);
     } else {
       // Check for HSTS upgrade.
-      TransportSecurityState* hsts =
-          request->context()->transport_security_state();
-      if (hsts && hsts->ShouldUpgradeToSSL(url.host(), request->net_log())) {
+      if (should_upgrade_to_ssl) {
+        RecordSTSHistogram(/*sts_enabled=*/true, /*is_secure=*/false,
+                           request->load_flags());
         return std::make_unique<URLRequestRedirectJob>(
             request, UpgradeSchemeToCryptographic(url),
             // Use status code 307 to preserve the method, so POST requests
@@ -258,12 +292,16 @@
     // ERR_CLEARTEXT_NOT_PERMITTED if not.
     if (request->context()->check_cleartext_permitted() &&
         !android::IsCleartextPermitted(url.host_piece())) {
+      RecordSTSHistogram(/*sts_enabled=*/false, /*is_secure=*/false,
+                         request->load_flags());
       return std::make_unique<URLRequestErrorJob>(request,
                                                   ERR_CLEARTEXT_NOT_PERMITTED);
     }
 #endif
   }
 
+  RecordSTSHistogram(should_upgrade_to_ssl, url.SchemeIsCryptographic(),
+                     request->load_flags());
   return base::WrapUnique<URLRequestJob>(new URLRequestHttpJob(
       request, request->context()->http_user_agent_settings()));
 }
diff --git a/services/network/ip_protection_config_cache.h b/services/network/ip_protection_config_cache.h
index 18ea3a43..6299d26 100644
--- a/services/network/ip_protection_config_cache.h
+++ b/services/network/ip_protection_config_cache.h
@@ -24,7 +24,7 @@
  public:
   virtual ~IpProtectionConfigCache() = default;
 
-  // Initializes the proxy list and token managers for the cache.
+  // Initializes the proxy chain list and token managers for the cache.
   virtual void SetUp() = 0;
 
   // Check whether tokens are available in all token caches.
@@ -55,20 +55,20 @@
   GetIpProtectionTokenCacheManagerForTesting(
       network::mojom::IpProtectionProxyLayer proxy_layer) = 0;
 
-  // Set the proxy list manager for the cache.
+  // Set the proxy chain list manager for the cache.
   virtual void SetIpProtectionProxyListManagerForTesting(
       std::unique_ptr<IpProtectionProxyListManager> ipp_proxy_list_manager) = 0;
 
-  // Check whether a proxy list is available.
+  // Check whether a proxy chain list is available.
   virtual bool IsProxyListAvailable() = 0;
 
-  // Return the currently cached proxy list. This contains a list of proxy
-  // hostnames. This list may be empty even if `IsProxyListAvailable()` returned
-  // true.
-  virtual const std::vector<std::string>& GetProxyList() = 0;
+  // Return the currently cached proxy chain lists. This contains the lists of
+  // hostnames corresponding to each proxy chain that should be used. This
+  // may be empty even if `IsProxyListAvailable()` returned true.
+  virtual std::vector<net::ProxyChain> GetProxyChainList() = 0;
 
-  // Request a refresh of the proxy list. Call this when it's likely that the
-  // proxy list is out of date.
+  // Request a refresh of the proxy chain list. Call this when it's likely that
+  // the proxy chain list is out of date.
   virtual void RequestRefreshProxyList() = 0;
 };
 
diff --git a/services/network/ip_protection_config_cache_impl.cc b/services/network/ip_protection_config_cache_impl.cc
index f0c00d9..d3ded71 100644
--- a/services/network/ip_protection_config_cache_impl.cc
+++ b/services/network/ip_protection_config_cache_impl.cc
@@ -8,6 +8,7 @@
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/time/time.h"
+#include "net/base/proxy_server.h"
 #include "services/network/ip_protection_proxy_list_manager.h"
 #include "services/network/ip_protection_proxy_list_manager_impl.h"
 #include "services/network/ip_protection_token_cache_manager.h"
@@ -92,11 +93,29 @@
              : false;
 }
 
-const std::vector<std::string>& IpProtectionConfigCacheImpl::GetProxyList() {
-  static const std::vector<std::string> empty_vector;
-  return (ipp_proxy_list_manager_ != nullptr)
-             ? ipp_proxy_list_manager_->ProxyList()
-             : empty_vector;
+std::vector<net::ProxyChain> IpProtectionConfigCacheImpl::GetProxyChainList() {
+  std::vector<net::ProxyChain> proxy_chain_list;
+  if (ipp_proxy_list_manager_ != nullptr) {
+    for (const std::vector<std::string>& proxy_chain_hostnames :
+         ipp_proxy_list_manager_->ProxyList()) {
+      bool invalid_proxy_server = false;
+      std::vector<net::ProxyServer> proxy_servers;
+      for (const auto& proxy : proxy_chain_hostnames) {
+        net::ProxyServer proxy_server = net::ProxyServer::FromSchemeHostAndPort(
+            net::ProxyServer::SCHEME_HTTPS, proxy, absl::nullopt);
+        // If invalid proxy server, skip entire proxy chain.
+        if (!proxy_server.is_valid()) {
+          invalid_proxy_server = true;
+          break;
+        }
+        proxy_servers.push_back(std::move(proxy_server));
+      }
+      if (!invalid_proxy_server) {
+        proxy_chain_list.emplace_back(std::move(proxy_servers));
+      }
+    }
+  }
+  return proxy_chain_list;
 }
 
 void IpProtectionConfigCacheImpl::RequestRefreshProxyList() {
diff --git a/services/network/ip_protection_config_cache_impl.h b/services/network/ip_protection_config_cache_impl.h
index 7184caf80..657e725 100644
--- a/services/network/ip_protection_config_cache_impl.h
+++ b/services/network/ip_protection_config_cache_impl.h
@@ -48,7 +48,7 @@
       std::unique_ptr<IpProtectionProxyListManager> ipp_proxy_list_manager)
       override;
   bool IsProxyListAvailable() override;
-  const std::vector<std::string>& GetProxyList() override;
+  std::vector<net::ProxyChain> GetProxyChainList() override;
   void RequestRefreshProxyList() override;
 
  private:
diff --git a/services/network/ip_protection_config_cache_impl_unittest.cc b/services/network/ip_protection_config_cache_impl_unittest.cc
index de675f0..af52d38 100644
--- a/services/network/ip_protection_config_cache_impl_unittest.cc
+++ b/services/network/ip_protection_config_cache_impl_unittest.cc
@@ -44,7 +44,9 @@
  public:
   bool IsProxyListAvailable() override { return proxy_list_.has_value(); }
 
-  const std::vector<std::string>& ProxyList() override { return *proxy_list_; }
+  const std::vector<std::vector<std::string>>& ProxyList() override {
+    return *proxy_list_;
+  }
 
   void RequestRefreshProxyList() override {
     if (on_force_refresh_proxy_list_) {
@@ -53,7 +55,7 @@
   }
 
   // Set the proxy list returned from `ProxyList()`.
-  void SetProxyList(std::vector<std::string> proxy_list) {
+  void SetProxyList(std::vector<std::vector<std::string>> proxy_list) {
     proxy_list_ = std::move(proxy_list);
   }
 
@@ -63,7 +65,7 @@
   }
 
  private:
-  absl::optional<std::vector<std::string>> proxy_list_;
+  absl::optional<std::vector<std::vector<std::string>>> proxy_list_;
   base::OnceClosure on_force_refresh_proxy_list_;
 };
 
@@ -122,15 +124,18 @@
 
 // Proxy list manager returns currently cached proxy hostnames.
 TEST_F(IpProtectionConfigCacheImplTest, GetProxyListFromManager) {
-  std::vector<std::string> exp_proxy_list = {"a-proxy"};
+  std::string proxy = "a-proxy";
+  const std::vector<net::ProxyChain> proxy_chain_list = {
+      net::ProxyChain(net::ProxyServer::FromSchemeHostAndPort(
+          net::ProxyServer::SCHEME_HTTPS, proxy, absl::nullopt))};
   auto ipp_proxy_list_manager_ =
       std::make_unique<MockIpProtectionProxyListManager>();
-  ipp_proxy_list_manager_->SetProxyList(exp_proxy_list);
+  ipp_proxy_list_manager_->SetProxyList({{proxy}});
   ipp_config_cache_->SetIpProtectionProxyListManagerForTesting(
       std::move(ipp_proxy_list_manager_));
 
   ASSERT_TRUE(ipp_config_cache_->IsProxyListAvailable());
-  EXPECT_EQ(ipp_config_cache_->GetProxyList(), exp_proxy_list);
+  EXPECT_EQ(ipp_config_cache_->GetProxyChainList(), proxy_chain_list);
 }
 
 }  // namespace network
diff --git a/services/network/ip_protection_proxy_list_manager.h b/services/network/ip_protection_proxy_list_manager.h
index cbb8099..6bf910e 100644
--- a/services/network/ip_protection_proxy_list_manager.h
+++ b/services/network/ip_protection_proxy_list_manager.h
@@ -22,7 +22,7 @@
 
   // Return the currently cached proxy list. This list may be empty even
   // if `IsProxyListAvailable()` returned true.
-  virtual const std::vector<std::string>& ProxyList() = 0;
+  virtual const std::vector<std::vector<std::string>>& ProxyList() = 0;
 
   // Request a refresh of the proxy list. Call this when it's likely that the
   // proxy list is out of date.
diff --git a/services/network/ip_protection_proxy_list_manager_impl.cc b/services/network/ip_protection_proxy_list_manager_impl.cc
index 9b9f82e..2b034a59 100644
--- a/services/network/ip_protection_proxy_list_manager_impl.cc
+++ b/services/network/ip_protection_proxy_list_manager_impl.cc
@@ -29,7 +29,8 @@
   return have_fetched_proxy_list_;
 }
 
-const std::vector<std::string>& IpProtectionProxyListManagerImpl::ProxyList() {
+const std::vector<std::vector<std::string>>&
+IpProtectionProxyListManagerImpl::ProxyList() {
   return proxy_list_;
 }
 
@@ -47,7 +48,7 @@
 }
 
 void IpProtectionProxyListManagerImpl::OnGotProxyList(
-    const absl::optional<std::vector<std::string>>& proxy_list) {
+    const absl::optional<std::vector<std::vector<std::string>>>& proxy_list) {
   fetching_proxy_list_ = false;
 
   // If an error occurred fetching the proxy list, continue using the existing
diff --git a/services/network/ip_protection_proxy_list_manager_impl.h b/services/network/ip_protection_proxy_list_manager_impl.h
index 759b16d..e986288 100644
--- a/services/network/ip_protection_proxy_list_manager_impl.h
+++ b/services/network/ip_protection_proxy_list_manager_impl.h
@@ -28,7 +28,7 @@
 
   // IpProtectionProxyListManager implementation.
   bool IsProxyListAvailable() override;
-  const std::vector<std::string>& ProxyList() override;
+  const std::vector<std::vector<std::string>>& ProxyList() override;
   void RequestRefreshProxyList() override;
 
   // Set a callback to occur when the proxy list has been refreshed.
@@ -45,10 +45,11 @@
 
  private:
   void RefreshProxyList();
-  void OnGotProxyList(const absl::optional<std::vector<std::string>>&);
+  void OnGotProxyList(
+      const absl::optional<std::vector<std::vector<std::string>>>&);
 
   // Latest fetched proxy list.
-  std::vector<std::string> proxy_list_;
+  std::vector<std::vector<std::string>> proxy_list_;
 
   // True if an invocation of `config_getter_.GetProxyList()` is
   // outstanding.
diff --git a/services/network/ip_protection_proxy_list_manager_impl_unittest.cc b/services/network/ip_protection_proxy_list_manager_impl_unittest.cc
index b47af72..7866d24 100644
--- a/services/network/ip_protection_proxy_list_manager_impl_unittest.cc
+++ b/services/network/ip_protection_proxy_list_manager_impl_unittest.cc
@@ -39,7 +39,8 @@
 
   // Register an expectation of a call to `GetIpProtectionProxyList()`,
   // returning the given proxy list manager.
-  void ExpectGetProxyListCall(std::vector<std::string> proxy_list) {
+  void ExpectGetProxyListCall(
+      std::vector<std::vector<std::string>> proxy_list) {
     expected_get_proxy_list_calls_.push_back(std::move(proxy_list));
   }
 
@@ -71,7 +72,7 @@
   }
 
  protected:
-  std::deque<absl::optional<std::vector<std::string>>>
+  std::deque<absl::optional<std::vector<std::vector<std::string>>>>
       expected_get_proxy_list_calls_;
 };
 
@@ -110,7 +111,7 @@
 
 // The manager gets the proxy list on startup and once again on schedule.
 TEST_F(IpProtectionProxyListManagerImplTest, ProxyListOnStartup) {
-  std::vector<std::string> exp_proxy_list = {"a-proxy"};
+  std::vector<std::vector<std::string>> exp_proxy_list = {{"a-proxy"}};
   mock_.ExpectGetProxyListCall(exp_proxy_list);
   ipp_proxy_list_->EnableProxyListRefreshingForTesting();
   WaitForProxyListRefresh();
@@ -119,27 +120,27 @@
   EXPECT_EQ(ipp_proxy_list_->ProxyList(), exp_proxy_list);
 
   base::Time start = base::Time::Now();
-  mock_.ExpectGetProxyListCall({"b-proxy"});
+  mock_.ExpectGetProxyListCall({{"b-proxy"}});
   WaitForProxyListRefresh();
   base::TimeDelta delay = net::features::kIpPrivacyProxyListFetchInterval.Get();
   EXPECT_EQ(base::Time::Now() - start, delay);
 
   ASSERT_TRUE(mock_.GotAllExpectedMockCalls());
   EXPECT_TRUE(ipp_proxy_list_->IsProxyListAvailable());
-  exp_proxy_list = {"b-proxy"};
+  exp_proxy_list = {{"b-proxy"}};
   EXPECT_EQ(ipp_proxy_list_->ProxyList(), exp_proxy_list);
 }
 
 // The manager refreshes the proxy list on demand, but only once even if
 // `RequestRefreshProxyList()` is called repeatedly.
 TEST_F(IpProtectionProxyListManagerImplTest, ProxyListRefresh) {
-  mock_.ExpectGetProxyListCall({"a-proxy"});
+  mock_.ExpectGetProxyListCall({{"a-proxy"}});
   ipp_proxy_list_->RequestRefreshProxyList();
   ipp_proxy_list_->RequestRefreshProxyList();
   WaitForProxyListRefresh();
   ASSERT_TRUE(mock_.GotAllExpectedMockCalls());
   EXPECT_TRUE(ipp_proxy_list_->IsProxyListAvailable());
-  std::vector<std::string> exp_proxy_list = {"a-proxy"};
+  std::vector<std::vector<std::string>> exp_proxy_list = {{"a-proxy"}};
   EXPECT_EQ(ipp_proxy_list_->ProxyList(), exp_proxy_list);
 }
 
@@ -154,7 +155,7 @@
 
 // The manager keeps its existing proxy list if it fails to fetch a new one.
 TEST_F(IpProtectionProxyListManagerImplTest, ProxyListKeptAfterFailure) {
-  std::vector<std::string> exp_proxy_list = {"a-proxy"};
+  std::vector<std::vector<std::string>> exp_proxy_list = {{"a-proxy"}};
   mock_.ExpectGetProxyListCall(exp_proxy_list);
   ipp_proxy_list_->RequestRefreshProxyList();
   WaitForProxyListRefresh();
diff --git a/services/network/masked_domain_list/network_service_proxy_allow_list.cc b/services/network/masked_domain_list/network_service_proxy_allow_list.cc
index d215ca6..ce65239 100644
--- a/services/network/masked_domain_list/network_service_proxy_allow_list.cc
+++ b/services/network/masked_domain_list/network_service_proxy_allow_list.cc
@@ -13,7 +13,10 @@
 
 namespace network {
 
-NetworkServiceProxyAllowList::NetworkServiceProxyAllowList() = default;
+NetworkServiceProxyAllowList::NetworkServiceProxyAllowList(
+    network::mojom::IpProtectionProxyBypassPolicy policy)
+    : proxy_bypass_policy_{policy} {}
+
 NetworkServiceProxyAllowList::~NetworkServiceProxyAllowList() = default;
 
 NetworkServiceProxyAllowList::NetworkServiceProxyAllowList(
@@ -21,7 +24,9 @@
 
 NetworkServiceProxyAllowList NetworkServiceProxyAllowList::CreateForTesting(
     std::map<std::string, std::set<std::string>> first_party_map) {
-  auto allow_list = NetworkServiceProxyAllowList();
+  auto allow_list = NetworkServiceProxyAllowList(
+      network::mojom::IpProtectionProxyBypassPolicy::
+          kFirstPartyToTopLevelFrame);
 
   for (auto const& [domain, properties] : first_party_map) {
     net::SchemeHostPortMatcher bypass_matcher;
@@ -75,21 +80,33 @@
 bool NetworkServiceProxyAllowList::Matches(
     const GURL& request_url,
     const net::NetworkAnonymizationKey& network_anonymization_key) {
-  if (!network_anonymization_key.GetTopFrameSite().has_value()) {
-    DVLOG(3) << "NSPAL::Matches(" << request_url
-             << ", empty top_frame_site) - false";
-    return false;
+  absl::optional<net::SchemefulSite> top_frame_site =
+      network_anonymization_key.GetTopFrameSite();
+  switch (proxy_bypass_policy_) {
+    case network::mojom::IpProtectionProxyBypassPolicy::kNone: {
+      return url_matcher_with_bypass_.Matches(request_url, top_frame_site, true)
+          .matches;
+    }
+    case network::mojom::IpProtectionProxyBypassPolicy::
+        kFirstPartyToTopLevelFrame: {
+      if (!network_anonymization_key.GetTopFrameSite().has_value()) {
+        DVLOG(3) << "NSPAL::Matches(" << request_url
+                 << ", empty top_frame_site) - false";
+        return false;
+      }
+      DVLOG(3) << "NSPAL::Matches(" << request_url << ", "
+               << top_frame_site.value() << ")";
+
+      // If the NAK is transient (has a nonce and/or top_frame_origin is
+      // opaque), we should skip the first party check and match only on the
+      // request_url.
+      UrlMatcherWithBypass::MatchResult result =
+          url_matcher_with_bypass_.Matches(
+              request_url, top_frame_site,
+              network_anonymization_key.IsTransient());
+      return result.matches && result.is_third_party;
+    }
   }
-
-  net::SchemefulSite top_frame_site =
-      network_anonymization_key.GetTopFrameSite().value();
-  DVLOG(3) << "NSPAL::Matches(" << request_url << ", " << top_frame_site << ")";
-
-  // If the NAK is transient (has a nonce and/or top_frame_origin is opaque), we
-  // should skip the first party check and match only on the request_url.
-  UrlMatcherWithBypass::MatchResult result = url_matcher_with_bypass_.Matches(
-      request_url, top_frame_site, network_anonymization_key.IsTransient());
-  return result.matches && result.is_third_party;
 }
 
 void NetworkServiceProxyAllowList::UseMaskedDomainList(
diff --git a/services/network/masked_domain_list/network_service_proxy_allow_list.h b/services/network/masked_domain_list/network_service_proxy_allow_list.h
index 58c825b..cf5b3a5 100644
--- a/services/network/masked_domain_list/network_service_proxy_allow_list.h
+++ b/services/network/masked_domain_list/network_service_proxy_allow_list.h
@@ -9,6 +9,7 @@
 #include "net/base/network_anonymization_key.h"
 #include "services/network/masked_domain_list/url_matcher_with_bypass.h"
 #include "services/network/public/mojom/network_context.mojom.h"
+#include "services/network/public/mojom/network_service.mojom-forward.h"
 
 namespace network {
 
@@ -18,7 +19,8 @@
 // Proxy and determines if pairs of request and top_frame URLs are eligible.
 class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkServiceProxyAllowList {
  public:
-  NetworkServiceProxyAllowList();
+  explicit NetworkServiceProxyAllowList(
+      network::mojom::IpProtectionProxyBypassPolicy);
   ~NetworkServiceProxyAllowList();
   NetworkServiceProxyAllowList(const NetworkServiceProxyAllowList&);
 
@@ -57,6 +59,9 @@
   void AddDomainWithBypass(const std::string& domain,
                            net::SchemeHostPortMatcher bypass_matcher);
 
+  // Policy that determines which domains are bypassed from IP Protection.
+  network::mojom::IpProtectionProxyBypassPolicy proxy_bypass_policy_;
+
   // Contains match rules from the Masked Domain List.
   UrlMatcherWithBypass url_matcher_with_bypass_;
 };
diff --git a/services/network/masked_domain_list/network_service_proxy_allow_list_unittest.cc b/services/network/masked_domain_list/network_service_proxy_allow_list_unittest.cc
index 676e88e..1fdbf6dc 100644
--- a/services/network/masked_domain_list/network_service_proxy_allow_list_unittest.cc
+++ b/services/network/masked_domain_list/network_service_proxy_allow_list_unittest.cc
@@ -9,6 +9,7 @@
 #include "net/base/network_anonymization_key.h"
 #include "net/base/schemeful_site.h"
 #include "services/network/public/cpp/features.h"
+#include "services/network/public/mojom/proxy_config.mojom-shared.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace network {
@@ -27,26 +28,39 @@
 
 class NetworkServiceProxyAllowListTest : public ::testing::Test {};
 
-TEST_F(NetworkServiceProxyAllowListTest, NotEnabled) {
-  NetworkServiceProxyAllowList allow_list;
-  EXPECT_FALSE(allow_list.IsEnabled());
+TEST_F(NetworkServiceProxyAllowListTest, IsNotEnabledByDefault) {
+  NetworkServiceProxyAllowList allow_list_no_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::kNone);
+  NetworkServiceProxyAllowList allow_list_first_party_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::
+          kFirstPartyToTopLevelFrame);
+  EXPECT_FALSE(allow_list_no_bypass.IsEnabled());
+  EXPECT_FALSE(allow_list_first_party_bypass.IsEnabled());
 }
 
-TEST_F(NetworkServiceProxyAllowListTest, IsEnabled) {
+TEST_F(NetworkServiceProxyAllowListTest, IsEnabledWhenManuallySet) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures({net::features::kEnableIpProtectionProxy,
                                         network::features::kMaskedDomainList},
                                        {});
 
-  NetworkServiceProxyAllowList allow_list;
+  NetworkServiceProxyAllowList allow_list(
+      network::mojom::IpProtectionProxyBypassPolicy::kNone);
 
   EXPECT_TRUE(allow_list.IsEnabled());
   EXPECT_TRUE(allow_list.MakeIpProtectionCustomProxyConfig()
                   ->rules.restrict_to_network_service_proxy_allow_list);
 }
 
-TEST_F(NetworkServiceProxyAllowListTest, IsPopulated) {
-  NetworkServiceProxyAllowList allow_list;
+TEST_F(NetworkServiceProxyAllowListTest, AllowListIsNotPopulatedByDefault) {
+  NetworkServiceProxyAllowList allow_list(
+      network::mojom::IpProtectionProxyBypassPolicy::kNone);
+  EXPECT_FALSE(allow_list.IsPopulated());
+}
+
+TEST_F(NetworkServiceProxyAllowListTest, AllowlistIsPopulatedWhenMDLUsed) {
+  NetworkServiceProxyAllowList allow_list(
+      network::mojom::IpProtectionProxyBypassPolicy::kNone);
   MaskedDomainList mdl;
   auto* resource_owner = mdl.add_resource_owners();
   resource_owner->set_owner_name("foo");
@@ -56,61 +70,114 @@
   EXPECT_TRUE(allow_list.IsPopulated());
 }
 
-TEST_F(NetworkServiceProxyAllowListTest, IsPopulated_Empty) {
-  NetworkServiceProxyAllowList allow_list;
-  EXPECT_FALSE(allow_list.IsPopulated());
-}
-
-TEST_F(NetworkServiceProxyAllowListTest, Matches) {
-  NetworkServiceProxyAllowList allow_list;
+TEST_F(NetworkServiceProxyAllowListTest, ShouldMatchThirdPartyToTopLevelFrame) {
+  NetworkServiceProxyAllowList allow_list_no_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::kNone);
+  NetworkServiceProxyAllowList allow_list_first_party_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::
+          kFirstPartyToTopLevelFrame);
   MaskedDomainList mdl;
   auto* resource_owner = mdl.add_resource_owners();
   resource_owner->set_owner_name("foo");
   resource_owner->add_owned_resources()->set_domain("example.com");
-  allow_list.UseMaskedDomainList(mdl);
+  allow_list_no_bypass.UseMaskedDomainList(mdl);
+  allow_list_first_party_bypass.UseMaskedDomainList(mdl);
 
-  EXPECT_TRUE(
-      allow_list.Matches(GURL("http://example.com"),
-                         net::NetworkAnonymizationKey::CreateCrossSite(
-                             net::SchemefulSite(GURL("http://top.com")))));
+  EXPECT_TRUE(allow_list_no_bypass.Matches(
+      GURL("http://example.com"),
+      net::NetworkAnonymizationKey::CreateCrossSite(
+          net::SchemefulSite(GURL("http://top.com")))));
+  EXPECT_TRUE(allow_list_first_party_bypass.Matches(
+      GURL("http://example.com"),
+      net::NetworkAnonymizationKey::CreateCrossSite(
+          net::SchemefulSite(GURL("http://top.com")))));
 }
 
-TEST_F(NetworkServiceProxyAllowListTest, Matches_EmptyNak) {
-  NetworkServiceProxyAllowList allow_list;
+TEST_F(NetworkServiceProxyAllowListTest,
+       MatchFirstPartyToTopLevelFrameDependsOnBypass) {
+  NetworkServiceProxyAllowList allow_list_no_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::kNone);
+  NetworkServiceProxyAllowList allow_list_first_party_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::
+          kFirstPartyToTopLevelFrame);
   MaskedDomainList mdl;
   auto* resource_owner = mdl.add_resource_owners();
   resource_owner->set_owner_name("foo");
   resource_owner->add_owned_resources()->set_domain("example.com");
-  allow_list.UseMaskedDomainList(mdl);
+  allow_list_no_bypass.UseMaskedDomainList(mdl);
+  allow_list_first_party_bypass.UseMaskedDomainList(mdl);
 
-  EXPECT_FALSE(allow_list.Matches(GURL("http://example.com"),
-                                  net::NetworkAnonymizationKey()));
+  EXPECT_TRUE(allow_list_no_bypass.Matches(
+      GURL("http://example.com"),
+      net::NetworkAnonymizationKey::CreateCrossSite(
+          net::SchemefulSite(GURL("http://example.com")))));
+  EXPECT_FALSE(allow_list_first_party_bypass.Matches(
+      GURL("http://example.com"),
+      net::NetworkAnonymizationKey::CreateCrossSite(
+          net::SchemefulSite(GURL("http://example.com")))));
 }
 
-TEST_F(NetworkServiceProxyAllowListTest, Matches_NoMatchWithTransientNak) {
-  NetworkServiceProxyAllowList allow_list;
+TEST_F(NetworkServiceProxyAllowListTest,
+       MatchFirstPartyToTopLevelFrameIfEmptyNakDependsOnBypass) {
+  NetworkServiceProxyAllowList allow_list_no_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::kNone);
+  NetworkServiceProxyAllowList allow_list_first_party_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::
+          kFirstPartyToTopLevelFrame);
   MaskedDomainList mdl;
   auto* resource_owner = mdl.add_resource_owners();
   resource_owner->set_owner_name("foo");
   resource_owner->add_owned_resources()->set_domain("example.com");
-  allow_list.UseMaskedDomainList(mdl);
+  allow_list_no_bypass.UseMaskedDomainList(mdl);
+  allow_list_first_party_bypass.UseMaskedDomainList(mdl);
 
-  EXPECT_FALSE(
-      allow_list.Matches(GURL("http://other.com"),
-                         net::NetworkAnonymizationKey::CreateTransient()));
+  EXPECT_TRUE(allow_list_no_bypass.Matches(GURL("http://example.com"),
+                                           net::NetworkAnonymizationKey()));
+  EXPECT_FALSE(allow_list_first_party_bypass.Matches(
+      GURL("http://example.com"), net::NetworkAnonymizationKey()));
 }
 
-TEST_F(NetworkServiceProxyAllowListTest, Matches_SkipBypassWithTransientNak) {
-  NetworkServiceProxyAllowList allowList;
+TEST_F(NetworkServiceProxyAllowListTest,
+       ShouldNotMatchWithTransientNakIfUrlDoesNotMatch) {
+  NetworkServiceProxyAllowList allow_list_no_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::kNone);
+  NetworkServiceProxyAllowList allow_list_first_party_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::
+          kFirstPartyToTopLevelFrame);
   MaskedDomainList mdl;
-  auto* resourceOwner = mdl.add_resource_owners();
-  resourceOwner->set_owner_name("foo");
-  resourceOwner->add_owned_resources()->set_domain("example.com");
-  allowList.UseMaskedDomainList(mdl);
+  auto* resource_owner = mdl.add_resource_owners();
+  resource_owner->set_owner_name("foo");
+  resource_owner->add_owned_resources()->set_domain("example.com");
+  allow_list_no_bypass.UseMaskedDomainList(mdl);
+  allow_list_first_party_bypass.UseMaskedDomainList(mdl);
 
-  EXPECT_TRUE(
-      allowList.Matches(GURL("http://example.com"),
-                        net::NetworkAnonymizationKey::CreateTransient()));
+  EXPECT_FALSE(allow_list_no_bypass.Matches(
+      GURL("http://other.com"),
+      net::NetworkAnonymizationKey::CreateTransient()));
+  EXPECT_FALSE(allow_list_first_party_bypass.Matches(
+      GURL("http://other.com"),
+      net::NetworkAnonymizationKey::CreateTransient()));
 }
 
+TEST_F(NetworkServiceProxyAllowListTest,
+       ShouldMatchWithTransientNakIfUrlMatches) {
+  NetworkServiceProxyAllowList allow_list_no_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::kNone);
+  NetworkServiceProxyAllowList allow_list_first_party_bypass(
+      network::mojom::IpProtectionProxyBypassPolicy::
+          kFirstPartyToTopLevelFrame);
+  MaskedDomainList mdl;
+  auto* resource_owner = mdl.add_resource_owners();
+  resource_owner->set_owner_name("foo");
+  resource_owner->add_owned_resources()->set_domain("example.com");
+  allow_list_no_bypass.UseMaskedDomainList(mdl);
+  allow_list_first_party_bypass.UseMaskedDomainList(mdl);
+
+  EXPECT_TRUE(allow_list_no_bypass.Matches(
+      GURL("http://example.com"),
+      net::NetworkAnonymizationKey::CreateTransient()));
+  EXPECT_TRUE(allow_list_first_party_bypass.Matches(
+      GURL("http://example.com"),
+      net::NetworkAnonymizationKey::CreateTransient()));
+}
 }  // namespace network
diff --git a/services/network/masked_domain_list/url_matcher_with_bypass.cc b/services/network/masked_domain_list/url_matcher_with_bypass.cc
index 73130cc..0c08fe4 100644
--- a/services/network/masked_domain_list/url_matcher_with_bypass.cc
+++ b/services/network/masked_domain_list/url_matcher_with_bypass.cc
@@ -7,7 +7,9 @@
 #include <string>
 #include <string_view>
 
+#include "base/check.h"
 #include "base/logging.h"
+#include "base/notreached.h"
 #include "base/strings/strcat.h"
 #include "base/trace_event/memory_usage_estimator.h"
 #include "components/privacy_sandbox/masked_domain_list/masked_domain_list.pb.h"
@@ -102,7 +104,7 @@
 
 UrlMatcherWithBypass::MatchResult UrlMatcherWithBypass::Matches(
     const GURL& request_url,
-    const net::SchemefulSite& top_frame_site,
+    const absl::optional<net::SchemefulSite>& top_frame_site,
     bool skip_bypass_check) {
   auto dvlog = [&](std::string_view message,
                    const UrlMatcherWithBypass::MatchResult& match_result) {
@@ -110,11 +112,16 @@
         {" - matches: ", match_result.matches ? "true" : "false",
          ", third-party: ", match_result.is_third_party ? "true" : "false"});
     DVLOG(3) << "UrlMatcherWithBypass::Matches(" << request_url << ", "
-             << top_frame_site << ") - " << message << result_message;
+             << top_frame_site.value() << ") - " << message << result_message;
   };
   // Result defaults to {matches = false, is_third_party = false}.
   MatchResult result;
 
+  if (!skip_bypass_check && !top_frame_site.has_value()) {
+    NOTREACHED_NORETURN()
+        << "top frame site has no value and skip_bypass_check is false";
+  }
+
   if (!IsPopulated()) {
     dvlog("skipped (match list not populated)", result);
     return result;
@@ -137,7 +144,7 @@
       result.matches = true;
       result.is_third_party =
           skip_bypass_check ||
-          bypass_matcher.Evaluate(top_frame_site.GetURL()) ==
+          bypass_matcher.Evaluate(top_frame_site.value().GetURL()) ==
               net::SchemeHostPortMatcherResult::kNoMatch;
       break;
     }
diff --git a/services/network/masked_domain_list/url_matcher_with_bypass.h b/services/network/masked_domain_list/url_matcher_with_bypass.h
index 7363849..028aeaa 100644
--- a/services/network/masked_domain_list/url_matcher_with_bypass.h
+++ b/services/network/masked_domain_list/url_matcher_with_bypass.h
@@ -44,8 +44,9 @@
   // resource_url and then checking if the top_frame_site matches the bypass
   // match rules. If skip_bypass_check is true, the top_frame_site will not be
   // used to determine the outcome of the match.
+  // top_frame_site should have a value if skip_bypass_check is false.
   MatchResult Matches(const GURL& resource_url,
-                      const net::SchemefulSite& top_frame_site,
+                      const absl::optional<net::SchemefulSite>& top_frame_site,
                       bool skip_bypass_check = false);
 
   // Adds a matcher rule and bypass matcher for the domain.
diff --git a/services/network/masked_domain_list/url_matcher_with_bypass_unittest.cc b/services/network/masked_domain_list/url_matcher_with_bypass_unittest.cc
index 1c873317..32929549 100644
--- a/services/network/masked_domain_list/url_matcher_with_bypass_unittest.cc
+++ b/services/network/masked_domain_list/url_matcher_with_bypass_unittest.cc
@@ -178,7 +178,8 @@
               false,
               {.matches = true, .is_third_party = true}},
 
-    // Third-party requests for properties in the MDL should not be proxied.
+    // Third-party requests for properties in the MDL should not be proxied
+    // if bypass policy is kFirstPartyToTopLevelFrame.
     MatchTest{"3PPropInOther",
               "acme-pa.com",
               "somehost.com",
@@ -207,7 +208,8 @@
 
     // As an exception, third-party requests for resources (including
     // subdomains) in the MDL should be not be proxied when the top-level site
-    // is a property with the same owner as the resource.
+    // is a property with the same owner as the resource if bypass policy is
+    // kFirstPartyToTopLevelFrame.
     MatchTest{"3PRsrcInPropSameOwner",
               "acme-ra.com",
               "acme-pa.com",
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index 10c97a08..80b98dd 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -486,7 +486,8 @@
       std::make_unique<FirstPartySetsManager>(params->first_party_sets_enabled);
 
   network_service_proxy_allow_list_ =
-      std::make_unique<NetworkServiceProxyAllowList>();
+      std::make_unique<NetworkServiceProxyAllowList>(
+          params->ip_protection_proxy_bypass_policy);
 
   network_service_resource_block_list_ =
       std::make_unique<NetworkServiceResourceBlockList>();
diff --git a/services/network/network_service_proxy_delegate.cc b/services/network/network_service_proxy_delegate.cc
index e2e70b68..761265b7 100644
--- a/services/network/network_service_proxy_delegate.cc
+++ b/services/network/network_service_proxy_delegate.cc
@@ -173,13 +173,14 @@
 
     net::ProxyList proxy_list;
     if (!net::features::kIpPrivacyDirectOnly.Get()) {
-      for (auto& proxy_hostname : ipp_config_cache_->GetProxyList()) {
-        proxy_list.AddProxyServer(net::ProxyServer::FromSchemeHostAndPort(
-            net::ProxyServer::SCHEME_HTTPS, proxy_hostname, absl::nullopt));
+      const std::vector<net::ProxyChain>& proxy_chain_list =
+          ipp_config_cache_->GetProxyChainList();
+      for (const auto& proxy_chain : proxy_chain_list) {
+        proxy_list.AddProxyChain(std::move(proxy_chain));
       }
     }
     // Final fallback is to DIRECT.
-    proxy_list.AddProxyServer(net::ProxyServer::Direct());
+    proxy_list.AddProxyChain(net::ProxyChain::Direct());
 
     if (VLOG_IS_ON(3)) {
       dvlog(base::StrCat({"setting proxy list (before deprioritization) to ",
@@ -229,8 +230,6 @@
     const net::ProxyChain& proxy_chain,
     size_t chain_index,
     net::HttpRequestHeaders* extra_headers) {
-  // TODO(crbug.com/1491092): Handle proxy chains.
-  CHECK(chain_index == 0);
 
   auto vlog = [](std::string message) {
     VLOG(2) << "NSPD::OnBeforeTunnelRequest() - " << message;
@@ -341,14 +340,7 @@
   if (!ipp_config_cache_) {
     return false;
   }
-
-  // This list will typically be quite short (2-3), so linear search is
-  // adequate.
-  // TODO(https://crbug.com/1491092): Update to support nested proxies.
-  CHECK(proxy_chain.is_single_proxy());
-  std::string proxy_server_host =
-      proxy_chain.GetProxyServer(/*chain_index=*/0).GetHost();
-  return base::Contains(ipp_config_cache_->GetProxyList(), proxy_server_host);
+  return base::Contains(ipp_config_cache_->GetProxyChainList(), proxy_chain);
 }
 
 bool NetworkServiceProxyDelegate::EligibleForProxy(
diff --git a/services/network/network_service_proxy_delegate_unittest.cc b/services/network/network_service_proxy_delegate_unittest.cc
index f30f1f21..e621903 100644
--- a/services/network/network_service_proxy_delegate_unittest.cc
+++ b/services/network/network_service_proxy_delegate_unittest.cc
@@ -72,8 +72,8 @@
     NOTREACHED_NORETURN();
   }
 
-  const std::vector<std::string>& GetProxyList() override {
-    return *proxy_list_;
+  std::vector<net::ProxyChain> GetProxyChainList() override {
+    return proxy_chain_list_;
   }
 
   bool IsProxyListAvailable() override { return proxy_list_.has_value(); }
@@ -85,8 +85,18 @@
   }
 
   // Set the proxy list returned from `ProxyList()`.
-  void SetProxyList(std::vector<std::string> proxy_list) {
+  void SetProxyList(std::vector<std::vector<std::string>> proxy_list) {
     proxy_list_ = std::move(proxy_list);
+    proxy_chain_list_.clear();
+    for (const auto& proxy_chain_hostnames : *proxy_list_) {
+      std::vector<net::ProxyServer> proxy_servers;
+      for (const auto& proxy : proxy_chain_hostnames) {
+        net::ProxyServer proxy_server = net::ProxyServer::FromSchemeHostAndPort(
+            net::ProxyServer::SCHEME_HTTPS, proxy, absl::nullopt);
+        proxy_servers.push_back(std::move(proxy_server));
+      }
+      proxy_chain_list_.emplace_back(std::move(proxy_servers));
+    }
   }
 
   void SetOnRequestRefreshProxyList(
@@ -96,7 +106,8 @@
 
  private:
   absl::optional<network::mojom::BlindSignedAuthTokenPtr> auth_token_;
-  absl::optional<std::vector<std::string>> proxy_list_;
+  absl::optional<std::vector<std::vector<std::string>>> proxy_list_;
+  std::vector<net::ProxyChain> proxy_chain_list_;
   base::OnceClosure on_force_refresh_proxy_list_;
 };
 
@@ -246,12 +257,15 @@
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({"proxy"});
+  ipp_config_cache->SetProxyList({{"proxya", "proxyb"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   net::HttpRequestHeaders headers;
-  auto proxy_chain = net::ProxyChain::FromSchemeHostAndPort(
-      net::ProxyServer::SCHEME_HTTPS, "proxy", absl::nullopt);
+  auto proxy_chain = net::ProxyChain(
+      {net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
+                                               "proxya", absl::nullopt),
+       net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
+                                               "proxyb", absl::nullopt)});
   delegate->OnBeforeTunnelRequest(proxy_chain, /*chain_index=*/0, &headers);
 
   EXPECT_THAT(headers, Contain("Authorization", "Bearer: a-token"));
@@ -275,6 +289,32 @@
   EXPECT_FALSE(headers.GetHeader("Authorization", &value));
 }
 
+TEST_F(NetworkServiceProxyDelegateTest,
+       OnResolveProxyDiscardsInvalidProxyServers) {
+  auto config =
+      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate =
+      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"foo:80"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("http://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_TRUE(result.is_for_ip_protection());
+}
+
 TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxySuccessHttpProxy) {
   auto config = mojom::CustomProxyConfig::New();
   config->rules.ParseFromString("http=foo");
@@ -629,7 +669,7 @@
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({"ippro-1", "ippro-2"});
+  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   net::ProxyInfo result;
@@ -675,7 +715,7 @@
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({"foo"});
+  ipp_config_cache->SetProxyList({{"foo"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   net::ProxyInfo result;
@@ -708,7 +748,7 @@
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({"ippro-1", "ippro-2"});
+  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   net::ProxyInfo result;
@@ -756,7 +796,7 @@
       CreateDelegate(std::move(config), &network_service_proxy_allow_list);
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetProxyList({"proxy"});
+  ipp_config_cache->SetProxyList({{"proxy"}});
   // No token is added to the cache, so the result will be direct.
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
@@ -814,7 +854,7 @@
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({"proxy"});
+  ipp_config_cache->SetProxyList({{"proxy"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   net::ProxyInfo result;
@@ -843,7 +883,7 @@
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({"ippro-1", "ippro-2"});
+  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   net::ProxyInfo result;
@@ -866,7 +906,7 @@
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({"ippro-1", "ippro-2"});
+  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   net::ProxyInfo result;
@@ -893,7 +933,7 @@
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({"ippro-1", "ippro-2"});
+  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   net::ProxyInfo result;
@@ -921,7 +961,7 @@
 
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({"proxy"});
+  ipp_config_cache->SetProxyList({{"proxy"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   net::ProxyInfo result;
@@ -981,7 +1021,7 @@
   auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
   ipp_config_cache->SetOnRequestRefreshProxyList(
       base::BindLambdaForTesting([&]() { force_refresh_called = true; }));
-  ipp_config_cache->SetProxyList({"proxy.com"});
+  ipp_config_cache->SetProxyList({{"proxy.com"}});
   delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
 
   delegate->OnFallback(proxy_chain, net::ERR_FAILED);
diff --git a/services/network/public/cpp/trigger_verification.h b/services/network/public/cpp/trigger_verification.h
index 8c8c728..f447d66c 100644
--- a/services/network/public/cpp/trigger_verification.h
+++ b/services/network/public/cpp/trigger_verification.h
@@ -35,10 +35,14 @@
   TriggerVerification& operator=(TriggerVerification&&);
 
   const std::string& token() const { return token_; }
+
   const base::Uuid& aggregatable_report_id() const {
     return aggregatable_report_id_;
   }
 
+  friend bool operator==(const TriggerVerification&,
+                         const TriggerVerification&) = default;
+
  private:
   TriggerVerification(std::string token, base::Uuid aggregatable_report_id);
 
diff --git a/services/network/public/cpp/trigger_verification_test_utils.cc b/services/network/public/cpp/trigger_verification_test_utils.cc
index 9b44dd4..f1cb402 100644
--- a/services/network/public/cpp/trigger_verification_test_utils.cc
+++ b/services/network/public/cpp/trigger_verification_test_utils.cc
@@ -8,13 +8,6 @@
 
 namespace network {
 
-bool operator==(const TriggerVerification&& a, const TriggerVerification&& b) {
-  auto tie = [](const TriggerVerification& t) {
-    return std::make_tuple(t.token(), t.aggregatable_report_id());
-  };
-  return tie(a) == tie(b);
-}
-
 std::ostream& operator<<(std::ostream& out,
                          const TriggerVerification& verification) {
   return out << "{token=" << verification.token() << ",aggregatable_report_id="
diff --git a/services/network/public/cpp/trigger_verification_test_utils.h b/services/network/public/cpp/trigger_verification_test_utils.h
index cd8b655..fc528dee8 100644
--- a/services/network/public/cpp/trigger_verification_test_utils.h
+++ b/services/network/public/cpp/trigger_verification_test_utils.h
@@ -5,13 +5,11 @@
 #ifndef SERVICES_NETWORK_PUBLIC_CPP_TRIGGER_VERIFICATION_TEST_UTILS_H_
 #define SERVICES_NETWORK_PUBLIC_CPP_TRIGGER_VERIFICATION_TEST_UTILS_H_
 
-#include <ostream>
+#include <iosfwd>
 
 namespace network {
 class TriggerVerification;
 
-bool operator==(const TriggerVerification&, const TriggerVerification&);
-
 std::ostream& operator<<(std::ostream&, const TriggerVerification&);
 }  // namespace network
 
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index f968fad..4060e51 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -922,13 +922,14 @@
     (array<BlindSignedAuthToken>? bsa_tokens,
      mojo_base.mojom.Time? try_again_after);
 
-  // Get the list of IP Protection proxies. The list contains proxy hostnames,
-  // in order of preference. Callers should prefer the first proxy, falling
-  // back to later proxies in the list. All proxies are implicitly HTTPS.
+  // Get the list of IP Protection proxy chains. The list contains lists of
+  // proxy chain hostnames, in order of preference. Callers should prefer
+  // the first proxy, falling back to later proxies in the list. All proxies
+  // are implicitly HTTPS.
   //
   // This method will return an up-to-date list, possibly fetching that list
   // remotely before returning it.
-  GetProxyList() => (array<string>? proxy_list);
+  GetProxyList() => (array<array<string>>? proxy_list);
 };
 
 // Represents a distinct context for making network requests, with its own
diff --git a/services/network/public/mojom/network_service.mojom b/services/network/public/mojom/network_service.mojom
index 3ebcbea6..e254166a 100644
--- a/services/network/public/mojom/network_service.mojom
+++ b/services/network/public/mojom/network_service.mojom
@@ -22,6 +22,7 @@
 import "services/network/public/mojom/network_context.mojom";
 import "services/network/public/mojom/ip_address_space.mojom";
 import "services/network/public/mojom/network_interface.mojom";
+import "services/network/public/mojom/proxy_config.mojom";
 import "services/network/public/mojom/network_interface_change_listener.mojom";
 import "services/network/public/mojom/network_param.mojom";
 import "services/network/public/mojom/network_quality_estimator_manager.mojom";
@@ -131,6 +132,11 @@
   // A SystemDnsResolver to provide out-of-process system DNS resolution, in
   // case the system DNS resolver cannot always run sandboxed.
   pending_remote<SystemDnsResolver>? system_dns_resolver;
+
+  // The policy used for bypassing requests that are eligible for IP Protection.
+  // Even if a domain is part of the masked domain list, the bypass policy can
+  // be used to bypass certain network requests from IP Protection.
+  IpProtectionProxyBypassPolicy ip_protection_proxy_bypass_policy;
 };
 
 // Configuration settings for SCT Auditing.
diff --git a/services/network/public/mojom/proxy_config.mojom b/services/network/public/mojom/proxy_config.mojom
index e2851b1..39ac43f 100644
--- a/services/network/public/mojom/proxy_config.mojom
+++ b/services/network/public/mojom/proxy_config.mojom
@@ -49,3 +49,14 @@
   bool pac_mandatory;
   ProxyRules proxy_rules;
 };
+
+// The policy used for bypassing requests that are eligible for IP Protection.
+// Even if a domain is part of the masked domain list, the bypass policy can
+// be used to bypass certain network requests from IP Protection.
+enum IpProtectionProxyBypassPolicy {
+  // No bypass policy.
+  kNone = 0,
+  // Request to domains that are first party to the top level frame are
+  // bypassed.
+  kFirstPartyToTopLevelFrame = 1,
+};
diff --git a/services/viz/public/cpp/compositing/shared_image_format_mojom_traits.cc b/services/viz/public/cpp/compositing/shared_image_format_mojom_traits.cc
index 5367ded..8f3b395 100644
--- a/services/viz/public/cpp/compositing/shared_image_format_mojom_traits.cc
+++ b/services/viz/public/cpp/compositing/shared_image_format_mojom_traits.cc
@@ -52,6 +52,10 @@
   switch (subsampling) {
     case viz::SharedImageFormat::Subsampling::k420:
       return viz::mojom::Subsampling::k420;
+    case viz::SharedImageFormat::Subsampling::k422:
+      return viz::mojom::Subsampling::k422;
+    case viz::SharedImageFormat::Subsampling::k444:
+      return viz::mojom::Subsampling::k444;
   }
   NOTREACHED();
   return viz::mojom::Subsampling::k420;
@@ -65,6 +69,12 @@
     case viz::mojom::Subsampling::k420:
       *out = viz::SharedImageFormat::Subsampling::k420;
       return true;
+    case viz::mojom::Subsampling::k422:
+      *out = viz::SharedImageFormat::Subsampling::k422;
+      return true;
+    case viz::mojom::Subsampling::k444:
+      *out = viz::SharedImageFormat::Subsampling::k444;
+      return true;
   }
   return false;
 }
diff --git a/services/viz/public/mojom/compositing/shared_image_format.mojom b/services/viz/public/mojom/compositing/shared_image_format.mojom
index 65dc739..3c0496f 100644
--- a/services/viz/public/mojom/compositing/shared_image_format.mojom
+++ b/services/viz/public/mojom/compositing/shared_image_format.mojom
@@ -10,7 +10,7 @@
 enum PlaneConfig { kY_U_V, kY_V_U, kY_UV, kY_UV_A };
 
 // See Subsampling in components/viz/common/resources/shared_image_format.h
-enum Subsampling { k420 };
+enum Subsampling { k420, k422, k444 };
 
 // See ChannelFormat in components/viz/common/resources/shared_image_format.h
 enum ChannelFormat { k8, k10, k16, k16F };
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 8bc02f2..a6f1bb3 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -6093,9 +6093,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6105,8 +6105,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
@@ -6243,9 +6243,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6255,8 +6255,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index 4861133745..627614e 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -20442,9 +20442,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20454,8 +20454,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
@@ -20592,9 +20592,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20604,8 +20604,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index c4cc0579..18a1baf6 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -43585,9 +43585,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43596,8 +43596,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
@@ -43735,9 +43735,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43746,8 +43746,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
@@ -45044,9 +45044,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -45055,8 +45055,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
@@ -45194,9 +45194,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -45205,8 +45205,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
@@ -45889,9 +45889,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -45900,8 +45900,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 00a2621..165fddb 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -16151,12 +16151,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16166,8 +16166,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
@@ -16321,12 +16321,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 121.0.6127.0",
+        "description": "Run with ash-chrome version 121.0.6128.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16336,8 +16336,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v121.0.6127.0",
-              "revision": "version:121.0.6127.0"
+              "location": "lacros_version_skew_tests_v121.0.6128.0",
+              "revision": "version:121.0.6128.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 1d7681f7..78217aa0 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -70,16 +70,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 121.0.6127.0',
+    'description': 'Run with ash-chrome version 121.0.6128.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6127.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v121.0.6128.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v121.0.6127.0',
-          'revision': 'version:121.0.6127.0',
+          'location': 'lacros_version_skew_tests_v121.0.6128.0',
+          'revision': 'version:121.0.6128.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index c28c17b..c3761d0a 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3586,15 +3586,15 @@
                 {
                     "name": "EnabledGroupA_20230911",
                     "params": {
-                        "CrOSZramWritebackWritebackBackoffTimeSec": "3600",
+                        "CrOSZramWritebackWritebackBackoffTimeSec": "300",
                         "ZramWritebackHuge": "false",
                         "ZramWritebackHugeIdle": "true",
                         "ZramWritebackIdle": "true",
-                        "ZramWritebackIdleMaxTimeSec": "108000",
+                        "ZramWritebackIdleMaxTimeSec": "86400",
                         "ZramWritebackIdleMinTimeSec": "64800",
-                        "ZramWritebackMaxPages": "76800",
-                        "ZramWritebackMinPages": "2560",
-                        "ZramWritebackPeriodicTimeSec": "1800"
+                        "ZramWritebackMaxPages": "25600",
+                        "ZramWritebackMinPages": "0",
+                        "ZramWritebackPeriodicTimeSec": "300"
                     },
                     "enable_features": [
                         "ChromeOSZramWriteback"
@@ -5472,6 +5472,7 @@
                     "name": "Enabled",
                     "params": {
                         "ShortcutBoostGroupWithSearches": "true",
+                        "ShortcutBoostNonTopHitSearchThreshold": "2",
                         "ShortcutBoostNonTopHitThreshold": "2",
                         "ShortcutBoostSearchScore": "1414",
                         "ShortcutBoostUrlScore": "1414",
@@ -8384,23 +8385,6 @@
             ]
         }
     ],
-    "IOSAutofillEnableCardArtAndCardProductName": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "AutofillEnableCardArtImage",
-                        "AutofillEnableCardProductName",
-                        "AutofillUseTwoDotsForLastFourDigits"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSBackgroundPasteboardAccess": [
         {
             "platforms": [
@@ -9886,6 +9870,26 @@
             ]
         }
     ],
+    "LiveCaptionExperimentalLanguages": [
+        {
+            "platforms": [
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "available_languages": "en-US,fr-FR,it-IT,de-DE,hi-IN,pt-BR,ja-JP"
+                    },
+                    "enable_features": [
+                        "LiveCaptionExperimentalLanguages"
+                    ]
+                }
+            ]
+        }
+    ],
     "LiveCaptionMultiLanguageRollout": [
         {
             "platforms": [
@@ -13474,7 +13478,6 @@
                         "AttributionReportingCrossAppWeb",
                         "BiddingAndScoringDebugReportingAPI",
                         "BrowsingTopics",
-                        "BrowsingTopicsXHR",
                         "FencedFrames",
                         "FencedFramesAPIChanges",
                         "FencedFramesEnforceFocus",
@@ -13504,7 +13507,6 @@
                         "AttributionFencedFrameReportingBeacon",
                         "AttributionReportingCrossAppWeb",
                         "BrowsingTopics",
-                        "BrowsingTopicsXHR",
                         "FencedFrames",
                         "FencedFramesAPIChanges",
                         "FencedFramesEnforceFocus",
diff --git a/third_party/angle b/third_party/angle
index 81e3ecf..a75659e 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 81e3ecff1c859823723d9abf3441626076400e69
+Subproject commit a75659eb40cc887619bb1e4d4efb91030891ced7
diff --git a/third_party/beto-core/src b/third_party/beto-core/src
index e0af43ba..96e6e37 160000
--- a/third_party/beto-core/src
+++ b/third_party/beto-core/src
@@ -1 +1 @@
-Subproject commit e0af43baab8771dd02d56532eab1a4f98d42ac9a
+Subproject commit 96e6e374c28a01caf35c7fb620eba078aeb655bb
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 9f2da5cd..cc14f043 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -320,7 +320,7 @@
 // Kill switch for the Topics API.
 BASE_FEATURE(kBrowsingTopics,
              "BrowsingTopics",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // If enabled, the check for whether the IP address is publicly routable will be
 // bypassed when determining the eligibility for a page to be included in topics
@@ -411,14 +411,6 @@
 const base::FeatureParam<std::string> kBrowsingTopicsPrioritizedTopicsList{
     &kBrowsingTopicsParameters, "prioritized_topics_list", ""};
 
-// Enables the deprecatedBrowsingTopics XHR attribute. For this feature to take
-// effect, the main Topics feature has to be enabled first (i.e.
-// `kBrowsingTopics` is enabled, and, either a valid Origin Trial token exists
-// or `kPrivacySandboxAdsAPIsOverride` is enabled.)
-BASE_FEATURE(kBrowsingTopicsXHR,
-             "BrowsingTopicsXHR",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Suppresses console errors for CORS problems which report an associated
 // inspector issue anyway.
 BASE_FEATURE(kCORSErrorsIssueOnly,
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 8280b127..b05a4d0 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -210,7 +210,6 @@
     kBrowsingTopicsDisabledTopicsList;
 BLINK_COMMON_EXPORT extern const base::FeatureParam<std::string>
     kBrowsingTopicsPrioritizedTopicsList;
-BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kBrowsingTopicsXHR);
 constexpr int kBrowsingTopicsTaxonomyVersionDefault = 2;
 
 // Suppresses console errors for CORS problems which report an associated
diff --git a/third_party/blink/public/mojom/loader/javascript_framework_detection.mojom b/third_party/blink/public/mojom/loader/javascript_framework_detection.mojom
index a865ef0..92560ddd 100644
--- a/third_party/blink/public/mojom/loader/javascript_framework_detection.mojom
+++ b/third_party/blink/public/mojom/loader/javascript_framework_detection.mojom
@@ -7,23 +7,27 @@
 // JS Frameworks (& CMSes) are detected at load time. This helps us correlate
 // performance metrics (and other metrics) with the existence and version of
 // frameworks on the page.
+//
+// Do not change the numeric value assignments, or reuse old values. The
+// auto speculation rules feature (https://crbug.com/1472970) uses these values
+// inside Finch configs.
 enum JavaScriptFramework {
-  kNuxt,
-  kVuePress,
-  kSapper,
-  kGatsby,
-  kNext,
-  kAngular,
-  kVue,
-  kSvelte,
-  kPreact,
-  kReact,
-  kDrupal,
-  kJoomla,
-  kShopify,
-  kSquarespace,
-  kWix,
-  kWordPress,
+  kNuxt = 0,
+  kVuePress = 1,
+  kSapper = 2,
+  kGatsby = 3,
+  kNext = 4,
+  kAngular = 5,
+  kVue = 6,
+  kSvelte = 7,
+  kPreact = 8,
+  kReact = 9,
+  kDrupal = 10,
+  kJoomla = 11,
+  kShopify = 12,
+  kSquarespace = 13,
+  kWix = 14,
+  kWordPress = 15,
 };
 
 // This struct is typemapped to blink::JavaScriptFrameworkDetectionResult.
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index 117ea39..6ac92ce8 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -3798,7 +3798,7 @@
   kOBSOLETE_V8RegExpUnicodeSetIncompatibilitiesWithUnicodeMode = 4458,
   kFedCmAutoReauthn = 4459,
   kTopicsAPIFetch = 4460,
-  kTopicsAPIXhr = 4461,
+  kOBSOLETE_TopicsAPIXhr = 4461,
   kParseFromString = 4462,
   kOBSOLETE_HTMLPatternRegExpUnicodeSetIncompatibilitiesWithUnicodeMode = 4463,
   kPopoverTypeAuto = 4464,
diff --git a/third_party/blink/renderer/core/dom/observable.cc b/third_party/blink/renderer/core/dom/observable.cc
index a5b664b..f0f03d7 100644
--- a/third_party/blink/renderer/core/dom/observable.cc
+++ b/third_party/blink/renderer/core/dom/observable.cc
@@ -42,8 +42,8 @@
 
   // Build and initialize a `Subscriber` with a dictionary of `Observer`
   // callbacks.
-  Subscriber* subscriber = MakeGarbageCollected<Subscriber>(
-      PassKey(), GetExecutionContext(), observer);
+  Subscriber* subscriber =
+      MakeGarbageCollected<Subscriber>(PassKey(), script_state, observer);
 
   DCHECK(subscribe_callback_);
   // Ordinarily we'd just invoke `subscribe_callback_` with
diff --git a/third_party/blink/renderer/core/dom/subscriber.cc b/third_party/blink/renderer/core/dom/subscriber.cc
index ffbbf4e8..4cbb227 100644
--- a/third_party/blink/renderer/core/dom/subscriber.cc
+++ b/third_party/blink/renderer/core/dom/subscriber.cc
@@ -16,13 +16,21 @@
 namespace blink {
 
 Subscriber::Subscriber(base::PassKey<Observable>,
-                       ExecutionContext* execution_context,
+                       ScriptState* script_state,
                        Observer* observer)
-    : ExecutionContextClient(execution_context),
+    : ExecutionContextClient(ExecutionContext::From(script_state)),
       next_(observer->hasNext() ? observer->next() : nullptr),
       complete_(observer->hasComplete() ? observer->complete() : nullptr),
-      error_(observer->hasError() ? observer->error() : nullptr),
-      signal_(observer->hasSignal() ? observer->signal() : nullptr) {}
+      error_(observer->hasError() ? observer->error() : nullptr) {
+  // Initialize `signal_` as a dependent signal on the input Observer's `signal`
+  // member, if it exists. See
+  // https://dom.spec.whatwg.org/#abortsignal-dependent-signals.
+  HeapVector<Member<AbortSignal>> signals;
+  if (observer->hasSignal()) {
+    signals.push_back(observer->signal());
+  }
+  signal_ = MakeGarbageCollected<AbortSignal>(script_state, signals);
+}
 
 void Subscriber::next(ScriptValue value) {
   if (next_) {
@@ -31,7 +39,7 @@
 }
 
 void Subscriber::complete() {
-  Member<V8ObserverCompleteCallback> complete = complete_;
+  V8ObserverCompleteCallback* complete = complete_;
   CloseSubscription();
 
   if (complete) {
@@ -40,7 +48,7 @@
 }
 
 void Subscriber::error(ScriptState* script_state, ScriptValue error_value) {
-  Member<V8ObserverCallback> error = error_;
+  V8ObserverCallback* error = error_;
   CloseSubscription();
 
   if (error) {
@@ -52,7 +60,7 @@
     //      it is optional)
     //   2. The subscription is already closed (in which case
     //      `CloseSubscription()` manually clears `error_`)
-    // In both of these cases, if the observer is still producing errors, we
+    // In both of these cases, if the observable is still producing errors, we
     // must surface them to the global via "report the exception":
     // https://html.spec.whatwg.org/C#report-the-exception.
     //
@@ -70,6 +78,8 @@
 }
 
 void Subscriber::CloseSubscription() {
+  active_ = false;
+
   // Reset all handlers, making it impossible to signal any more values to the
   // subscriber.
   next_ = nullptr;
diff --git a/third_party/blink/renderer/core/dom/subscriber.h b/third_party/blink/renderer/core/dom/subscriber.h
index 69a9686..4d204fb2 100644
--- a/third_party/blink/renderer/core/dom/subscriber.h
+++ b/third_party/blink/renderer/core/dom/subscriber.h
@@ -16,9 +16,9 @@
 namespace blink {
 
 class AbortSignal;
-class ExecutionContext;
 class Observable;
 class Observer;
+class ScriptState;
 class V8ObserverCallback;
 class V8ObserverCompleteCallback;
 
@@ -27,7 +27,7 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  Subscriber(base::PassKey<Observable>, ExecutionContext*, Observer*);
+  Subscriber(base::PassKey<Observable>, ScriptState*, Observer*);
 
   // API methods.
   void next(ScriptValue);
@@ -35,6 +35,7 @@
   void error(ScriptState*, ScriptValue);
 
   // API attributes.
+  bool active() { return active_; }
   AbortSignal* signal() { return signal_.Get(); }
 
   void Trace(Visitor*) const override;
@@ -49,8 +50,14 @@
   Member<V8ObserverCompleteCallback> complete_;
   Member<V8ObserverCallback> error_;
 
-  // Null if `signal` is not passed into `Observable::subscribe()` via the
-  // `Observer` dictionary.
+  // This starts out true, and becomes false only once `Subscriber::{complete(),
+  // error()}` are called (just before the corresponding `Observer` callbacks
+  // are invoked) or once the subscriber unsubscribes by aborting the
+  // `AbortSignal` that it passed into `Observable::subscribe()`.
+  bool active_ = true;
+
+  // This is never null. It is exposed via the `signal` WebIDL attribute, and
+  // represents whether or not the current subscription has been aborted or not.
   Member<AbortSignal> signal_;
 };
 
diff --git a/third_party/blink/renderer/core/dom/subscriber.idl b/third_party/blink/renderer/core/dom/subscriber.idl
index 6c4c608..580814c 100644
--- a/third_party/blink/renderer/core/dom/subscriber.idl
+++ b/third_party/blink/renderer/core/dom/subscriber.idl
@@ -9,6 +9,8 @@
   void next(any result);
   void complete();
   [CallWith=ScriptState] void error(any error);
-  readonly attribute AbortSignal? signal;
+
+  readonly attribute boolean active;
+  readonly attribute AbortSignal signal;
 };
 
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
index 87d85922..9f9115c 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -147,6 +147,11 @@
 #endif
 );
 
+// Kill switch for not requesting continuous begin frame for low latency canvas.
+BASE_FEATURE(kLowLatencyCanvasNoBeginFrameKillSwitch,
+             "LowLatencyCanvasNoBeginFrameKillSwitch",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // These values come from the WhatWG spec.
 constexpr int kDefaultCanvasWidth = 300;
 constexpr int kDefaultCanvasHeight = 150;
@@ -519,9 +524,13 @@
         surface_layer_bridge_->GetFrameSinkId().client_id(),
         surface_layer_bridge_->GetFrameSinkId().sink_id(),
         CanvasResourceDispatcher::kInvalidPlaceholderCanvasId, Size());
-    // We don't actually need the begin frame signal when in low latency mode,
-    // but we need to subscribe to it or else dispatching frames will not work.
-    frame_dispatcher_->SetNeedsBeginFrame(IsPageVisible());
+    if (!base::FeatureList::IsEnabled(
+            kLowLatencyCanvasNoBeginFrameKillSwitch)) {
+      // We don't actually need the begin frame signal when in low latency mode,
+      // but we need to subscribe to it or else dispatching frames will not
+      // work.
+      frame_dispatcher_->SetNeedsBeginFrame(IsPageVisible());
+    }
 
     UseCounter::Count(GetDocument(), WebFeature::kHTMLCanvasElementLowLatency);
   }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
index 61e6933..07cb7e4 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
@@ -292,6 +292,9 @@
   PropagateFromFragment(child, child_offset, *relative_offset,
                         inline_container);
   AddChildInternal(&child, child_offset + *relative_offset);
+
+  // We have got some content, so follow normal breaking rules from now on.
+  SetRequiresContentBeforeBreaking(false);
 }
 
 void NGBoxFragmentBuilder::AddBreakToken(const NGBreakToken* token,
@@ -610,8 +613,6 @@
         -previous_consumed_block_size);
     descendant.static_position.offset.block_offset +=
         previous_consumed_block_size;
-    descendant.containing_block.SetRequiresContentBeforeBreaking(
-        RequiresContentBeforeBreaking());
   }
 
   // If the fixedpos containing block is fragmented, adjust the offset to be
@@ -621,8 +622,6 @@
        descendant.fixedpos_inline_container.container)) {
     descendant.fixedpos_containing_block.IncreaseBlockOffset(
         -previous_consumed_block_size);
-    descendant.fixedpos_containing_block.SetRequiresContentBeforeBreaking(
-        RequiresContentBeforeBreaking());
   }
 }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
index 710450f..2085fe1 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.h
@@ -247,17 +247,6 @@
     return is_known_to_fit_in_fragmentainer_;
   }
 
-  // True if we need to keep some child content in the current fragmentainer
-  // before breaking (even that overflows the fragmentainer). We'll do this by
-  // refusing last-resort breaks when there's no container separation, and we'll
-  // instead overflow the fragmentainer. See MustStayInCurrentFragmentainer().
-  void SetRequiresContentBeforeBreaking(bool b) {
-    requires_content_before_breaking_ = b;
-  }
-  bool RequiresContentBeforeBreaking() const {
-    return requires_content_before_breaking_;
-  }
-
   void SetIsBlockSizeForFragmentationClamped() {
     is_block_size_for_fragmentation_clamped_ = true;
   }
@@ -655,7 +644,6 @@
   bool is_initial_block_size_indefinite_ = false;
   bool is_inline_formatting_context_;
   bool is_known_to_fit_in_fragmentainer_ = false;
-  bool requires_content_before_breaking_ = false;
   bool is_block_size_for_fragmentation_clamped_ = false;
   bool is_monolithic_ = true;
   bool is_first_for_node_ = true;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.cc
index 083b3062..4032503 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.cc
@@ -376,7 +376,7 @@
   DCHECK(child);
   oof_positioned_candidates_.emplace_back(
       child, LogicalStaticPosition{child_offset, inline_edge, block_edge},
-      NGInlineContainer<LogicalOffset>());
+      RequiresContentBeforeBreaking(), NGInlineContainer<LogicalOffset>());
 }
 
 void NGFragmentBuilder::AddOutOfFlowChildCandidate(
@@ -458,7 +458,9 @@
       // as a fragmentainer descendant instead.
       DCHECK(!candidate.inline_container.container);
       destination_builder->AddOutOfFlowFragmentainerDescendant(
-          {node, candidate.static_position, multicol->fixedpos_inline_container,
+          {node, candidate.static_position,
+           !!candidate.requires_content_before_breaking,
+           multicol->fixedpos_inline_container,
            multicol->fixedpos_containing_block,
            multicol->fixedpos_containing_block,
            multicol->fixedpos_inline_container});
@@ -575,9 +577,10 @@
         if (fixedpos_inline_container)
           new_fixedpos_inline_container = *fixedpos_inline_container;
         AddOutOfFlowFragmentainerDescendant(
-            {node, static_position, new_fixedpos_inline_container,
-             *fixedpos_containing_block, *fixedpos_containing_block,
-             new_fixedpos_inline_container});
+            {node, static_position,
+             !!descendant.requires_content_before_breaking,
+             new_fixedpos_inline_container, *fixedpos_containing_block,
+             *fixedpos_containing_block, new_fixedpos_inline_container});
         continue;
       }
     }
@@ -586,8 +589,9 @@
     // |oof_positioned_candidates_| should not have duplicated entries.
     DCHECK(!base::Contains(oof_positioned_candidates_, node,
                            &NGLogicalOutOfFlowPositionedNode::Node));
-    oof_positioned_candidates_.emplace_back(node, static_position,
-                                            new_inline_container);
+    oof_positioned_candidates_.emplace_back(
+        node, static_position, descendant.requires_content_before_breaking,
+        new_inline_container);
   }
 
   NGFragmentedOutOfFlowData* oof_data = fragment.FragmentedOutOfFlowData();
@@ -666,9 +670,7 @@
                   fixedpos_containing_block_rel_offset,
                   fixedpos_containing_block_fragment,
                   fixedpos_clipped_container_block_offset,
-                  is_inside_column_spanner,
-                  multicol_info->fixedpos_containing_block
-                      .RequiresContentBeforeBreaking()),
+                  is_inside_column_spanner),
               new_fixedpos_inline_container));
     }
   }
@@ -845,20 +847,18 @@
           fixedpos_containing_block->RelativeOffset();
     }
     NGLogicalOOFNodeForFragmentation oof_node(
-        descendant.Node(), static_position, new_inline_container,
+        descendant.Node(), static_position,
+        descendant.requires_content_before_breaking, new_inline_container,
         NGContainingBlock<LogicalOffset>(
             containing_block_offset, containing_block_rel_offset,
             containing_block_fragment, clipped_container_block_offset,
-            container_inside_column_spanner,
-            descendant.containing_block.RequiresContentBeforeBreaking()),
+            container_inside_column_spanner),
         NGContainingBlock<LogicalOffset>(
             fixedpos_containing_block_offset,
             fixedpos_containing_block_rel_offset,
             fixedpos_containing_block_fragment,
             fixedpos_clipped_container_block_offset,
-            fixedpos_container_inside_column_spanner,
-            descendant.fixedpos_containing_block
-                .RequiresContentBeforeBreaking()),
+            fixedpos_container_inside_column_spanner),
         new_fixedpos_inline_container);
 
     if (out_list) {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h
index c0bb7e29..84322e6 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h
@@ -429,6 +429,17 @@
     return should_force_same_fragmentation_flow_;
   }
 
+  // True if we need to keep some child content in the current fragmentainer
+  // before breaking (even that overflows the fragmentainer). We'll do this by
+  // refusing last-resort breaks when there's no container separation, and we'll
+  // instead overflow the fragmentainer. See MustStayInCurrentFragmentainer().
+  void SetRequiresContentBeforeBreaking(bool b) {
+    requires_content_before_breaking_ = b;
+  }
+  bool RequiresContentBeforeBreaking() const {
+    return requires_content_before_breaking_;
+  }
+
   // Downgrade the break appeal if the specified break appeal is lower than any
   // found so far.
   void ClampBreakAppeal(NGBreakAppeal appeal) {
@@ -625,6 +636,7 @@
   bool has_column_spanner_ = false;
   bool is_empty_spanner_parent_ = false;
   bool should_force_same_fragmentation_flow_ = false;
+  bool requires_content_before_breaking_ = false;
   bool should_add_break_tokens_manually_ = false;
   bool has_out_of_flow_fragment_child_ = false;
   bool has_out_of_flow_in_fragmentainer_subtree_ = false;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index cca6a92d..f6523cd 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -1116,22 +1116,20 @@
       NGLogicalOOFNodeForFragmentation node = {
           descendant.Node(),
           static_position,
+          !!descendant.requires_content_before_breaking,
           inline_container,
           NGContainingBlock<LogicalOffset>(
               containing_block_offset, containing_block_rel_offset,
               containing_block_fragment,
               descendant.containing_block.ClippedContainerBlockOffset(),
-              descendant.containing_block.IsInsideColumnSpanner(),
-              descendant.containing_block.RequiresContentBeforeBreaking()),
+              descendant.containing_block.IsInsideColumnSpanner()),
           NGContainingBlock<LogicalOffset>(
               fixedpos_containing_block_offset,
               fixedpos_containing_block_rel_offset,
               fixedpos_containing_block_fragment,
               descendant.fixedpos_containing_block
                   .ClippedContainerBlockOffset(),
-              descendant.fixedpos_containing_block.IsInsideColumnSpanner(),
-              descendant.fixedpos_containing_block
-                  .RequiresContentBeforeBreaking()),
+              descendant.fixedpos_containing_block.IsInsideColumnSpanner()),
           fixedpos_inline_container};
       oof_nodes_to_layout.push_back(node);
     }
@@ -1568,8 +1566,6 @@
   PhysicalSize container_physical_content_size = ToPhysicalSize(
       container_content_size, ConstraintSpace().GetWritingMode());
 
-  bool requires_content_before_breaking = false;
-
   // Adjust the |static_position| (which is currently relative to the default
   // container's border-box). ng_absolute_utils expects the static position to
   // be relative to the container's padding-box. Since
@@ -1581,8 +1577,6 @@
     const auto& containing_block_for_fragmentation =
         To<NGLogicalOOFNodeForFragmentation>(oof_node).containing_block;
     static_position.offset += containing_block_for_fragmentation.Offset();
-    requires_content_before_breaking =
-        containing_block_for_fragmentation.RequiresContentBeforeBreaking();
   }
 
   LogicalStaticPosition oof_static_position =
@@ -1620,13 +1614,14 @@
                                     .fixedpos_inline_container;
   }
 
-  return NodeInfo(
-      node, builder.ToConstraintSpace(), oof_static_position,
-      container_physical_content_size, container_info,
-      ConstraintSpace().GetWritingDirection(),
-      /* is_fragmentainer_descendant */ containing_block_fragment,
-      containing_block, fixedpos_containing_block, fixedpos_inline_container,
-      oof_node.inline_container.container, requires_content_before_breaking);
+  return NodeInfo(node, builder.ToConstraintSpace(), oof_static_position,
+                  container_physical_content_size, container_info,
+                  ConstraintSpace().GetWritingDirection(),
+                  /* is_fragmentainer_descendant */ containing_block_fragment,
+                  containing_block, fixedpos_containing_block,
+                  fixedpos_inline_container,
+                  oof_node.inline_container.container,
+                  oof_node.requires_content_before_breaking);
 }
 
 const NGLayoutResult* NGOutOfFlowLayoutPart::LayoutOOFNode(
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h
index 2e465a9..47007eb 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h
@@ -33,15 +33,13 @@
                     OffsetType relative_offset,
                     const NGPhysicalFragment* fragment,
                     absl::optional<LayoutUnit> clipped_container_block_offset,
-                    bool is_inside_column_spanner,
-                    bool requires_content_before_breaking)
+                    bool is_inside_column_spanner)
       : offset_(offset),
         relative_offset_(relative_offset),
         fragment_(std::move(fragment)),
         clipped_container_block_offset_(
             clipped_container_block_offset.value_or(LayoutUnit::Min())),
-        is_inside_column_spanner_(is_inside_column_spanner),
-        requires_content_before_breaking_(requires_content_before_breaking) {}
+        is_inside_column_spanner_(is_inside_column_spanner) {}
 
   OffsetType Offset() const { return offset_; }
   void IncreaseBlockOffset(LayoutUnit block_offset) {
@@ -57,13 +55,6 @@
   }
   bool IsInsideColumnSpanner() const { return is_inside_column_spanner_; }
 
-  void SetRequiresContentBeforeBreaking(bool b) {
-    requires_content_before_breaking_ = b;
-  }
-  bool RequiresContentBeforeBreaking() const {
-    return requires_content_before_breaking_;
-  }
-
   // True if the containing block of an OOF is inside a clipped container inside
   // a fragmentation context.
   // For example: <multicol><clipped-overflow-container><relpos><abspos>
@@ -84,10 +75,6 @@
   // True if there is a column spanner between the containing block and the
   // multicol container (or if the containing block is a column spanner).
   bool is_inside_column_spanner_ = false;
-  // True if we need to keep some child content in the current fragmentainer
-  // before breaking (even that overflows the fragmentainer). See
-  // NGBoxFragmentBuilder::SetRequiresContentBeforeBreaking() for more details.
-  bool requires_content_before_breaking_ = false;
 };
 
 // This holds the containing block for an out-of-flow positioned element
@@ -173,11 +160,13 @@
   unsigned static_position_vertical_edge : 2;
   // Whether or not this is an NGPhysicalOOFNodeForFragmentation.
   unsigned is_for_fragmentation : 1;
+  unsigned requires_content_before_breaking : 1;
   NGInlineContainer<PhysicalOffset> inline_container;
 
   NGPhysicalOutOfFlowPositionedNode(
       NGBlockNode node,
       PhysicalStaticPosition static_position,
+      bool requires_content_before_breaking,
       NGInlineContainer<PhysicalOffset> inline_container =
           NGInlineContainer<PhysicalOffset>())
       : box(node.GetLayoutBox()),
@@ -185,6 +174,7 @@
         static_position_horizontal_edge(static_position.horizontal_edge),
         static_position_vertical_edge(static_position.vertical_edge),
         is_for_fragmentation(false),
+        requires_content_before_breaking(requires_content_before_breaking),
         inline_container(inline_container) {
     DCHECK(node.IsBlock());
   }
@@ -221,15 +211,19 @@
   // Whether or not this is an NGLogicalOOFNodeForFragmentation.
   unsigned is_for_fragmentation : 1;
 
+  unsigned requires_content_before_breaking : 1;
+
   NGLogicalOutOfFlowPositionedNode(
       NGBlockNode node,
       LogicalStaticPosition static_position,
+      bool requires_content_before_breaking,
       NGInlineContainer<LogicalOffset> inline_container =
           NGInlineContainer<LogicalOffset>())
       : box(node.GetLayoutBox()),
         static_position(static_position),
         inline_container(inline_container),
-        is_for_fragmentation(false) {
+        is_for_fragmentation(false),
+        requires_content_before_breaking(requires_content_before_breaking) {
     DCHECK(node.IsBlock());
   }
 
@@ -268,6 +262,7 @@
   NGPhysicalOOFNodeForFragmentation(
       NGBlockNode node,
       PhysicalStaticPosition static_position,
+      bool requires_content_before_breaking,
       NGInlineContainer<PhysicalOffset> inline_container =
           NGInlineContainer<PhysicalOffset>(),
       NGContainingBlock<PhysicalOffset> containing_block =
@@ -278,6 +273,7 @@
           NGInlineContainer<PhysicalOffset>())
       : NGPhysicalOutOfFlowPositionedNode(node,
                                           static_position,
+                                          requires_content_before_breaking,
                                           inline_container),
         containing_block(containing_block),
         fixedpos_containing_block(fixedpos_containing_block),
@@ -306,6 +302,7 @@
   NGLogicalOOFNodeForFragmentation(
       NGBlockNode node,
       LogicalStaticPosition static_position,
+      bool requires_content_before_breaking,
       NGInlineContainer<LogicalOffset> inline_container =
           NGInlineContainer<LogicalOffset>(),
       NGContainingBlock<LogicalOffset> containing_block =
@@ -316,6 +313,7 @@
           NGInlineContainer<LogicalOffset>())
       : NGLogicalOutOfFlowPositionedNode(node,
                                          static_position,
+                                         requires_content_before_breaking,
                                          inline_container),
         containing_block(containing_block),
         fixedpos_containing_block(fixedpos_containing_block),
@@ -325,9 +323,11 @@
 
   explicit NGLogicalOOFNodeForFragmentation(
       const NGLogicalOutOfFlowPositionedNode& oof_node)
-      : NGLogicalOutOfFlowPositionedNode(oof_node.Node(),
-                                         oof_node.static_position,
-                                         oof_node.inline_container) {
+      : NGLogicalOutOfFlowPositionedNode(
+            oof_node.Node(),
+            oof_node.static_position,
+            oof_node.requires_content_before_breaking,
+            oof_node.inline_container) {
     is_for_fragmentation = true;
   }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
index d9d98a0..06219b6 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
@@ -105,8 +105,7 @@
                               builder->Style().GetWritingDirection()),
       containing_block.Fragment(),
       containing_block.ClippedContainerBlockOffset(),
-      containing_block.IsInsideColumnSpanner(),
-      containing_block.RequiresContentBeforeBreaking());
+      containing_block.IsInsideColumnSpanner());
 }
 
 NGContainingBlock<PhysicalOffset> PhysicalContainingBlock(
@@ -475,7 +474,7 @@
         descendant.Node(),
         descendant.static_position.ConvertToPhysical(
             containing_block_converter),
-        inline_container,
+        descendant.requires_content_before_breaking, inline_container,
         PhysicalContainingBlock(builder, size, containing_block_size,
                                 descendant.containing_block),
         PhysicalContainingBlock(builder, size,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
index e266008..2c879eb 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_fragment.cc
@@ -401,7 +401,7 @@
       oof_data->oof_positioned_descendants.emplace_back(
           descendant.Node(),
           descendant.static_position.ConvertToPhysical(converter),
-          inline_container);
+          descendant.requires_content_before_breaking, inline_container);
     }
   }
 
diff --git a/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc b/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc
index 9e7ada4c..0d3d5f8 100644
--- a/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc
+++ b/third_party/blink/renderer/core/origin_trials/origin_trial_context.cc
@@ -562,10 +562,6 @@
       restricted.push_back(mojom::blink::OriginTrialFeature::kTopicsAPI);
     }
     if (!base::FeatureList::IsEnabled(features::kBrowsingTopics) ||
-        !base::FeatureList::IsEnabled(features::kBrowsingTopicsXHR)) {
-      restricted.push_back(mojom::blink::OriginTrialFeature::kTopicsXHR);
-    }
-    if (!base::FeatureList::IsEnabled(features::kBrowsingTopics) ||
         !base::FeatureList::IsEnabled(features::kBrowsingTopicsDocumentAPI)) {
       restricted.push_back(
           mojom::blink::OriginTrialFeature::kTopicsDocumentAPI);
diff --git a/third_party/blink/renderer/core/page/plugin_data.cc b/third_party/blink/renderer/core/page/plugin_data.cc
index e0f6303..97a915d 100644
--- a/third_party/blink/renderer/core/page/plugin_data.cc
+++ b/third_party/blink/renderer/core/page/plugin_data.cc
@@ -23,7 +23,6 @@
 
 #include "third_party/blink/renderer/core/page/plugin_data.h"
 
-#include "base/metrics/histogram_macros.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
 #include "third_party/blink/public/mojom/plugins/plugin_registry.mojom-blink.h"
@@ -100,8 +99,6 @@
 void PluginData::UpdatePluginList() {
   if (updated_)
     return;
-
-  SCOPED_UMA_HISTOGRAM_TIMER("Blink.Plugin.UpdateTime");
   ResetPluginData();
   updated_ = true;
 
diff --git a/third_party/blink/renderer/core/paint/outline_painter.cc b/third_party/blink/renderer/core/paint/outline_painter.cc
index 1e63282..0cb904f 100644
--- a/third_party/blink/renderer/core/paint/outline_painter.cc
+++ b/third_party/blink/renderer/core/paint/outline_painter.cc
@@ -821,11 +821,7 @@
                     const ComputedStyle& style,
                     const FloatRoundedRect::Radii& corner_radii,
                     const LayoutObject::OutlineInfo& info) {
-  Color inner_color = style.VisitedDependentColor(GetCSSPropertyOutlineColor());
-#if !BUILDFLAG(IS_MAC)
-  if (style.DarkColorScheme())
-    inner_color = Color::kWhite;
-#endif
+  Color inner_color = style.OutlineColorForAutoStyle();
 
   const float outer_ring_width = FocusRingOuterStrokeWidth(style);
   const float inner_ring_width = FocusRingInnerStrokeWidth(style);
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 34410a8..5e17fd0 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -2506,6 +2506,41 @@
          (IsA<HTMLLIElement>(parent) && !IsInsideListElement());
 }
 
+std::optional<blink::Color> ComputedStyle::OutlineColorResolved() const {
+  const StyleColor color = OutlineColor();
+  if (color.HasColorKeyword()) {
+    if (color.GetColorKeyword() == CSSValueID::kAuto ||
+        color.GetColorKeyword() == CSSValueID::kCurrentcolor) {
+      return std::nullopt;
+    }
+  }
+  return color.Resolve(GetCurrentColor(), UsedColorScheme());
+}
+
+blink::Color ComputedStyle::OutlineColorForAutoStyle() const {
+  // https://www.w3.org/TR/css-ui-4/#propdef-outline-color
+  // says to use accent-color if outline-style is auto and outline-color is auto
+  blink::Color inner_color =
+      VisitedDependentColor(GetCSSPropertyOutlineColor());
+
+  if (OutlineStyleIsAuto()) {
+    std::optional<blink::Color> outline_color = OutlineColorResolved();
+#if !BUILDFLAG(IS_MAC)
+    if (DarkColorScheme()) {
+      inner_color = Color::kWhite;
+    }
+#endif
+
+    if (!outline_color) {
+      std::optional<blink::Color> accent_color = AccentColorResolved();
+      if (accent_color) {
+        inner_color = *accent_color;
+      }
+    }
+  }
+  return inner_color;
+}
+
 absl::optional<blink::Color> ComputedStyle::AccentColorResolved() const {
   const StyleAutoColor& auto_color = AccentColor();
   if (auto_color.IsAutoColor()) {
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index aac5ad93..c81ce48 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -912,6 +912,12 @@
   // An empty optional means the accent-color is 'auto'
   absl::optional<blink::Color> AccentColorResolved() const;
 
+  // outline-color
+  // An empty optional means the outline-color is 'auto'
+  absl::optional<blink::Color> OutlineColorResolved() const;
+
+  blink::Color OutlineColorForAutoStyle() const;
+
   // scrollbar-color
   // An empty optional means the scrollbar-color is 'auto'
   absl::optional<blink::Color> ScrollbarThumbColorResolved() const;
diff --git a/third_party/blink/renderer/core/timing/responsiveness_metrics.cc b/third_party/blink/renderer/core/timing/responsiveness_metrics.cc
index 64e8e19..a1bc4fb2 100644
--- a/third_party/blink/renderer/core/timing/responsiveness_metrics.cc
+++ b/third_party/blink/renderer/core/timing/responsiveness_metrics.cc
@@ -23,6 +23,7 @@
 #include "third_party/blink/renderer/core/timing/performance_event_timing.h"
 #include "third_party/blink/renderer/core/timing/window_performance.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
+#include "third_party/perfetto/include/perfetto/tracing/track.h"
 
 namespace blink {
 
@@ -185,6 +186,8 @@
                                         total_event_duration, interaction_type),
                "frame", GetFrameIdForTracing(window->GetFrame()));
 
+  EmitInteractionToNextPaintTraceEvent(longest_event, interaction_type,
+                                       total_event_duration);
   // Emit a trace event when "interaction to next paint" is considered "slow"
   // according to RAIL guidelines (web.dev/rail).
   constexpr base::TimeDelta kSlowInteractionToNextPaintThreshold =
@@ -516,4 +519,41 @@
   visitor->Trace(pointer_flush_timer_);
 }
 
+perfetto::protos::pbzero::WebContentInteraction::Type
+ResponsivenessMetrics::UserInteractionTypeToProto(
+    UserInteractionType interaction_type) const {
+  using Interaction = perfetto::protos::pbzero::WebContentInteraction;
+  switch (interaction_type) {
+    case UserInteractionType::kDrag:
+      return Interaction::INTERACTION_DRAG;
+    case UserInteractionType::kKeyboard:
+      return Interaction::INTERACTION_KEYBOARD;
+    case UserInteractionType::kTapOrClick:
+      return Interaction::INTERACTION_CLICK_TAP;
+  }
+
+  return Interaction::INTERACTION_UNSPECIFIED;
+}
+
+void ResponsivenessMetrics::EmitInteractionToNextPaintTraceEvent(
+    const ResponsivenessMetrics::EventTimestamps& event,
+    UserInteractionType interaction_type,
+    base::TimeDelta total_event_duration) {
+  const perfetto::Track track(base::trace_event::GetNextGlobalTraceId(),
+                              perfetto::ProcessTrack::Current());
+  TRACE_EVENT_BEGIN(
+      "interactions", "Web Interaction", track, event.start_time,
+      [&](perfetto::EventContext& ctx) {
+        auto* web_content_interaction =
+            ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>()
+                ->set_web_content_interaction();
+        web_content_interaction->set_type(
+            UserInteractionTypeToProto(interaction_type));
+        web_content_interaction->set_total_duration_ms(
+            total_event_duration.InMilliseconds());
+      });
+
+  TRACE_EVENT_END("interactions", track, event.end_time);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/responsiveness_metrics.h b/third_party/blink/renderer/core/timing/responsiveness_metrics.h
index 7faff64..793474f 100644
--- a/third_party/blink/renderer/core/timing/responsiveness_metrics.h
+++ b/third_party/blink/renderer/core/timing/responsiveness_metrics.h
@@ -6,12 +6,15 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_RESPONSIVENESS_METRICS_H_
 
 #include "base/time/time.h"
+#include "base/trace_event/typed_macros.h"
+#include "base/tracing/protos/chrome_track_event.pbzero.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/responsiveness_metrics/user_interaction_latency.h"
 #include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
 #include "third_party/blink/renderer/core/events/pointer_event.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
+#include "third_party/perfetto/include/perfetto/tracing/event_context.h"
 
 namespace blink {
 
@@ -128,6 +131,14 @@
 
   void Trace(Visitor*) const;
 
+  perfetto::protos::pbzero::WebContentInteraction::Type
+  UserInteractionTypeToProto(UserInteractionType interaction_type) const;
+
+  void EmitInteractionToNextPaintTraceEvent(
+      const ResponsivenessMetrics::EventTimestamps& event,
+      UserInteractionType interaction_type,
+      base::TimeDelta total_event_duration);
+
  private:
   // Record UKM for user interaction latencies.
   void RecordUserInteractionUKM(
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
index 1d83323..a090b4cf 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
@@ -635,10 +635,6 @@
   with_credentials_ = value;
 }
 
-void XMLHttpRequest::setDeprecatedBrowsingTopics(bool value) {
-  deprecated_browsing_topics_ = value;
-}
-
 void XMLHttpRequest::open(const AtomicString& method,
                           const String& url_string,
                           ExceptionState& exception_state) {
@@ -1098,11 +1094,6 @@
   request.SetCredentialsMode(
       with_credentials_ ? network::mojom::CredentialsMode::kInclude
                         : network::mojom::CredentialsMode::kSameOrigin);
-  request.SetBrowsingTopics(deprecated_browsing_topics_);
-  if (deprecated_browsing_topics_) {
-    UseCounter::Count(&execution_context, WebFeature::kTopicsAPIXhr);
-  }
-
   request.SetSkipServiceWorker(world_ && world_->IsIsolatedWorld());
   if (trust_token_params_)
     request.SetTrustTokenParams(*trust_token_params_);
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.h b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.h
index c576015..b1a3bf0 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.h
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.h
@@ -126,8 +126,6 @@
   State readyState() const;
   bool withCredentials() const { return with_credentials_; }
   void setWithCredentials(bool, ExceptionState&);
-  bool deprecatedBrowsingTopics() const { return deprecated_browsing_topics_; }
-  void setDeprecatedBrowsingTopics(bool);
   void open(const AtomicString& method, const String& url, ExceptionState&);
   void open(const AtomicString& method,
             const String& url,
@@ -362,8 +360,6 @@
 
   bool with_credentials_ = false;
 
-  bool deprecated_browsing_topics_ = false;
-
   network::mojom::AttributionReportingEligibility
       attribution_reporting_eligibility_ =
           network::mojom::AttributionReportingEligibility::kUnset;
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.idl b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.idl
index 41d15d8..b81ee89f 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.idl
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.idl
@@ -64,7 +64,6 @@
     [RaisesException, RuntimeEnabled=AttributionReportingInterface, SecureContext] void setAttributionReporting(AttributionReportingRequestOptions attributionReporting);
     [RaisesException=Setter] attribute unsigned long timeout;
     [RaisesException=Setter] attribute boolean withCredentials;
-    [RuntimeEnabled=TopicsXHR, SecureContext, Exposed=Window] attribute boolean deprecatedBrowsingTopics;
     readonly attribute XMLHttpRequestUpload upload;
     [RaisesException] void send(optional (Document or XMLHttpRequestBodyInit)? body = null);
     void abort();
diff --git a/third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module_test.cc b/third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module_test.cc
index 2289028..9dce4bf4 100644
--- a/third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module_test.cc
+++ b/third_party/blink/renderer/modules/canvas/htmlcanvas/html_canvas_element_module_test.cc
@@ -132,11 +132,6 @@
   CanvasContextCreationAttributesCore attrs;
   attrs.alpha = context_alpha;
   attrs.desynchronized = true;
-  // |context_| creation triggers a SurfaceLayerBridge creation which connects
-  // to a MockEmbeddedFrameSinkProvider to create a new CompositorFrameSink,
-  // that will receive a SetNeedsBeginFrame() upon construction.
-  mock_embedded_frame_sink_provider
-      .set_num_expected_set_needs_begin_frame_on_sink_construction(1);
   EXPECT_CALL(mock_embedded_frame_sink_provider, CreateCompositorFrameSink_(_));
   context_ = canvas_element().GetCanvasRenderingContext(String("2d"), attrs);
   EXPECT_EQ(context_->CreationAttributes().alpha, attrs.alpha);
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 569b14f..7cbe5a7 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -3679,13 +3679,6 @@
       origin_trial_allows_third_party: true,
       public: true,
     },
-    {
-      name: "TopicsXHR",
-      base_feature: "none",
-      origin_trial_feature_name: "PrivacySandboxAdsAPIs",
-      origin_trial_allows_third_party: true,
-      public: true,
-    },
     // This feature allows touch dragging and a context menu to occur
     // simultaneously, with the assumption that the menu is non-modal.  Without
     // this feature, a long-press touch gesture can start either a drag or a
diff --git a/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/analyzer.py b/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/analyzer.py
index 929c3d05..593b601 100644
--- a/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/analyzer.py
+++ b/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/analyzer.py
@@ -77,13 +77,14 @@
         # Total distinct image diff number does not reach the threshold, return all
         # these image diff as suggested matching.
         if len(image_diff_counts) < self._distinct_diff_num_threshold:
-            result = 'Total image diff number is %d. ' % total_image_diff_num
-            result += 'Total distinct image diff number is less '
+            result = 'Total test number that have image diff is '
+            result += '%d. ' % total_image_diff_num
+            result += 'Total distinct image diff results is less '
             result += 'than %d. Suggested ' % self._distinct_diff_num_threshold
-            result += 'make all following image diff (color_difference, '
-            result += 'pixel_difference) to match actual image result:'
+            result += 'check these image diff result (color_difference, '
+            result += 'pixel_difference) individually to fix the issue:'
             for image_diff, image_diff_number in image_diff_counts.items():
-                result = result + ' (%d, %d) with total %d' % (
+                result = result + ' (%d, %d) with total test number %d' % (
                     image_diff.color_difference, image_diff.pixel_difference,
                     image_diff_number)
             return dt.TestAnalysisResultType(True, result)
@@ -95,7 +96,8 @@
             color_diff_list += [image_diff.color_difference] * count
             pixel_diff_list += [image_diff.pixel_difference] * count
 
-        result = 'Total image diff number is %d. ' % total_image_diff_num
+        result = 'Total test number that have image diff is '
+        result += '%d. ' % total_image_diff_num
         result += 'The list of fuzzy match range suggested for this test: '
         result += '\nFor color difference:\n'
         result += self._calculate_data_percentile(color_diff_list)
diff --git a/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/analyzer_unittest.py b/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/analyzer_unittest.py
index 0ea7945..928ad55d 100644
--- a/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/analyzer_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/analyzer_unittest.py
@@ -48,11 +48,13 @@
         }
         actual_result = fuzzy_match_analyzer.run_analyzer(
             test_data).analysis_result
-        expected_result = 'Total image diff number is 3. ' \
-                          'Total distinct image diff number is less than 4. ' \
-                          'Suggested make all following image diff ' \
-                          '(color_difference, pixel_difference) to match actual ' \
-                          'image result: (10, 40) with total 2 (15, 42) with total 1'
+        expected_result = 'Total test number that have image diff is 3. ' \
+                          'Total distinct image diff results is less than 4. ' \
+                          'Suggested check these image diff result ' \
+                          '(color_difference, pixel_difference) ' \
+                          'individually to fix the issue: (10, 40) ' \
+                          'with total test number 2 ' \
+                          '(15, 42) with total test number 1'
         self.assertEqual(actual_result, expected_result)
 
     def test_run_analyzer_in_fuzzy_match_range_in_different_platform(
@@ -78,7 +80,8 @@
         }
         actual_result = fuzzy_match_analyzer.run_analyzer(
             test_data).analysis_result
-        expected_result = 'Total image diff number is 5. The list of fuzzy match range suggested for this ' \
+        expected_result = 'Total test number that have image diff is 5. ' \
+                          'The list of fuzzy match range suggested for this ' \
                           'test: \nFor color difference:\n15 to cover 50 percentile, 16 ' \
                           'to cover 75 percentile, 16 to cover 90 percentile, ' \
                           '16 to cover 95 percentile, 303 to cover all.\nFor ' \
@@ -101,7 +104,8 @@
         }
         actual_result = fuzzy_match_analyzer.run_analyzer(
             test_data).analysis_result
-        expected_result = 'Total image diff number is 100. The list of fuzzy match range suggested for this ' \
+        expected_result = 'Total test number that have image diff is 100. ' \
+                          'The list of fuzzy match range suggested for this ' \
                           'test: \nFor color difference:\n50 to cover 50 percentile, 75 ' \
                           'to cover 75 percentile, 90 to cover 90 percentile, ' \
                           '95 to cover 95 percentile, 100 to cover all.\nFor ' \
@@ -124,7 +128,8 @@
         }
         actual_result = fuzzy_match_analyzer.run_analyzer(
             test_data).analysis_result
-        expected_result = 'Total image diff number is 101. The list of fuzzy match range suggested for this ' \
+        expected_result = 'Total test number that have image diff is 101. ' \
+                          'The list of fuzzy match range suggested for this ' \
                           'test: \nFor color difference:\n51 to cover 50 percentile, 76 ' \
                           'to cover 75 percentile, 91 to cover 90 percentile, ' \
                           '96 to cover 95 percentile, 101 to cover all.\nFor ' \
diff --git a/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/fuzzy_diff_analyzer.py b/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/fuzzy_diff_analyzer.py
index 43b3aed..fe3b826 100644
--- a/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/fuzzy_diff_analyzer.py
+++ b/third_party/blink/tools/blinkpy/web_tests/fuzzy_diff_analyzer/fuzzy_diff_analyzer.py
@@ -106,6 +106,7 @@
         bugs = {'': [args.test_path]}
 
     for bug_id, test_list in bugs.items():
+        bug_result_string = ''
         for test_path in test_list:
             query_results = (querier_instance.
                              get_failed_image_comparison_ci_tests(test_path))
@@ -116,23 +117,26 @@
                 test_analysis_result = matching_analyzer.run_analyzer(
                     test_data)
                 if test_analysis_result.is_analyzed:
-                    result_string = RESULT_TITLE
-                    if bug_id:
-                        result_string += '\nbug number: %s' % bug_id
-                    result_string += '\ntest_name: %s' % test_name
-                    result_string += '\ntest_result: %s' % \
+                    result_string = ''
+                    if bug_id and not args.attach_analysis_result:
+                        result_string += '\nBug number: %s' % bug_id
+                    result_string += '\nTest name: %s' % test_name
+                    result_string += '\nTest Result: %s' % \
                                      test_analysis_result.analysis_result
-                    result_string += '\ndashboard_link: %s\n' % \
+                    result_string += '\nDashboard link: %s\n' % \
                                      (DASHBOARD_BASE_URL +
                                       '?f=test_name_cgk78f:re:' +
                                       urllib.parse.quote(test_name, safe=''))
-                    if args.attach_analysis_result:
-                        if RESULT_TITLE not in str(
-                                monorail_api.get_comment_list(
-                                    'chromium', bug_id)):
-                            monorail_api.insert_comment(
-                                'chromium', bug_id, result_string)
-                    else:
+                    if not args.attach_analysis_result:
                         print(result_string)
+                    else:
+                        bug_result_string += result_string
+        # Attach the analysis result for this bug.
+        if bug_id and args.attach_analysis_result:
+            bug_result_string = RESULT_TITLE + bug_result_string
+            if RESULT_TITLE not in str(
+                    monorail_api.get_comment_list('chromium', bug_id)):
+                monorail_api.insert_comment('chromium', bug_id,
+                                            bug_result_string)
 
     return 0
diff --git a/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter.py b/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter.py
index 6f323b3..7d92dd1 100644
--- a/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter.py
+++ b/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter.py
@@ -173,10 +173,10 @@
             self.options.smoke = self.port.default_smoke_test_only()
         if self.options.smoke:
             if not self.paths and not self.options.test_list and self.options.num_retries is None:
-                # Retry failures 3 times if we're running a smoke test without
+                # Retry failures 1 times if we're running a smoke test without
                 # additional tests. SmokeTests is an explicit list of tests, so we
                 # wouldn't retry by default without this special case.
-                self.options.num_retries = 3
+                self.options.num_retries = 1
 
             if not self.options.test_list:
                 self.options.test_list = []
@@ -402,10 +402,10 @@
 
         if self.options.num_retries is None:
             # If --test-list is passed, or if no test narrowing is specified,
-            # default to 3 retries. Otherwise [e.g. if tests are being passed by
+            # default to 1 retries. Otherwise [e.g. if tests are being passed by
             # name], default to 0 retries.
             if self.options.test_list or len(self.paths) < len(all_test_names):
-                self.options.num_retries = 3
+                self.options.num_retries = 1
             else:
                 self.options.num_retries = 0
 
diff --git a/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter_unittest.py b/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter_unittest.py
index 14ae119..4d873e29 100644
--- a/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter_unittest.py
+++ b/third_party/blink/tools/blinkpy/wpt_tests/wpt_adapter_unittest.py
@@ -355,7 +355,7 @@
         adapter = WPTAdapter.from_args(
             self.host, ['--product=content_shell', '--no-manifest-update'])
         with adapter.test_env() as options:
-            self.assertEqual(options.retry_unexpected, 3)
+            self.assertEqual(options.retry_unexpected, 1)
 
         # TODO We should not retry failures when running with '--use-upstream-wpt'
         # Consider add a unit test for that
@@ -364,7 +364,7 @@
             self.host,
             ['--product=content_shell', '--no-manifest-update', '--smoke'])
         with adapter.test_env() as options:
-            self.assertEqual(options.retry_unexpected, 3)
+            self.assertEqual(options.retry_unexpected, 1)
 
         adapter = WPTAdapter.from_args(self.host, [
             '--product=content_shell', '--no-manifest-update',
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 2a97ea6..2eca30ec 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1772,7 +1772,7 @@
     "exclusive_tests": [
       "external/wpt/browsing-topics"
     ],
-    "args": ["--enable-features=BrowsingTopics,BrowsingTopicsXHR,PrivacySandboxAdsAPIsOverride",
+    "args": ["--enable-features=BrowsingTopics,PrivacySandboxAdsAPIsOverride",
              "--disable-threaded-compositing", "--disable-threaded-animation"],
     "expires": "Aug 10, 2023"
   },
diff --git a/third_party/blink/web_tests/external/wpt/browsing-topics/xhr-topics-insecure-context.tentative.http.html b/third_party/blink/web_tests/external/wpt/browsing-topics/xhr-topics-insecure-context.tentative.http.html
deleted file mode 100644
index 5e32b83f..0000000
--- a/third_party/blink/web_tests/external/wpt/browsing-topics/xhr-topics-insecure-context.tentative.http.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!doctype html>
-<body>
-  <script src=/resources/testharness.js></script>
-  <script src=/resources/testharnessreport.js></script>
-  <script>
-    promise_test(async t => {
-      const xhr = new XMLHttpRequest();
-
-      let xhr_response = new Promise((resolve, reject) => {
-        xhr.onreadystatechange = function() {
-          if (xhr.readyState == XMLHttpRequest.DONE) {
-            // The request was not eligible for topics, as the context is not
-            // secure.
-            assert_equals(xhr.responseText, "NO_TOPICS_HEADER");
-            resolve();
-          }
-        }
-      });
-
-      xhr.open('GET', './resources/check-topics-request-header.py');
-      xhr.deprecatedBrowsingTopics = true;
-      xhr.send();
-
-      await xhr_response;
-    }, 'test XHR in insecure context that sets the deprecatedBrowsingTopics attribtue');
-  </script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/browsing-topics/xhr-topics.tentative.https.html b/third_party/blink/web_tests/external/wpt/browsing-topics/xhr-topics.tentative.https.html
deleted file mode 100644
index fcca74a..0000000
--- a/third_party/blink/web_tests/external/wpt/browsing-topics/xhr-topics.tentative.https.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!doctype html>
-<body>
-  <script src=/resources/testharness.js></script>
-  <script src=/resources/testharnessreport.js></script>
-  <script src=/browsing-topics/resources/header-util.sub.js></script>
-  <script>
-    promise_test(async t => {
-      const xhr = new XMLHttpRequest();
-
-      let xhr_response = new Promise((resolve, reject) => {
-        xhr.onreadystatechange = function() {
-          if (xhr.readyState == XMLHttpRequest.DONE) {
-            // An empty result indicates that the request was eligible for
-            // topics. Currently, the web-platform-tests framework does not
-            // support actually handling the topics request.
-            assert_equals(xhr.responseText, EMPTY_TOPICS_HEADER);
-            resolve();
-          }
-        }
-      });
-
-
-      xhr.open('GET', './resources/check-topics-request-header.py');
-      xhr.deprecatedBrowsingTopics = true;
-      xhr.send();
-
-      await xhr_response;
-    }, 'test XHR that sets the deprecatedBrowsingTopics attribtue');
-  </script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/browsing-topics/xhr-without-topics-attribute.tentative.https.html b/third_party/blink/web_tests/external/wpt/browsing-topics/xhr-without-topics-attribute.tentative.https.html
deleted file mode 100644
index 47bc1049..0000000
--- a/third_party/blink/web_tests/external/wpt/browsing-topics/xhr-without-topics-attribute.tentative.https.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!doctype html>
-<body>
-  <script src=/resources/testharness.js></script>
-  <script src=/resources/testharnessreport.js></script>
-  <script>
-    promise_test(async t => {
-      const xhr = new XMLHttpRequest();
-
-      let xhr_response = new Promise((resolve, reject) => {
-        xhr.onreadystatechange = function() {
-          if (xhr.readyState == XMLHttpRequest.DONE) {
-            // The request was not eligible for topics, as the
-            // `deprecatedBrowsingTopics` attribtue was not set.
-            assert_equals(xhr.responseText, "NO_TOPICS_HEADER");
-            resolve();
-          }
-        }
-      });
-
-      xhr.open('GET', './resources/check-topics-request-header.py');
-      xhr.send();
-
-      await xhr_response;
-    }, 'test XHR that does not set the deprecatedBrowsingTopics attribtue');
-  </script>
-</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-015.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-015.html
deleted file mode 100644
index 64c32c0..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-015.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<title>
-  Tests that a flexbox expands its intrinsic block-size, due to a
-  flex item fragmenting.
-</title>
-<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#pagination">
-<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
-<style>
-  #flex {
-    display: flex;
-    flex-wrap: wrap;
-    background: green;
-  }
-  #flex > div {
-    width: 10px;
-  }
-</style>
-<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
-<div style="width: 100px; height: 100px; columns: 5; column-gap: 0; column-fill: auto; background: red;">
-  <div id="flex">
-    <div>
-      <div style="contain: size; width: 10px; height: 80px;"></div>
-      <div style="contain: size; width: 10px; height: 30px;"></div>
-    </div>
-    <div>
-      <div style="contain: size; width: 10px; height: 70px;"></div>
-      <div style="contain: size; width: 10px; height: 40px;"></div>
-    </div>
-    <div>
-      <div style="contain: size; width: 10px; height: 40px;"></div>
-      <div style="contain: size; width: 10px; height: 80px;"></div>
-    </div>
-    <div>
-      <div style="contain: size; width: 10px; height: 80px;"></div>
-      <div style="contain: size; width: 10px; height: 30px;"></div>
-    </div>
-    <div style="height: 100px; width: 20px;"></div>
-    <div style="height: 20px; width: 20px;"></div>
-  </div>
-</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-077.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-077.html
new file mode 100644
index 0000000..4a0b056
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-077.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1500947">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<style>
+  .flex {
+    display: flex;
+    flex-flow: row wrap;
+  }
+  .flex > div {
+    flex: 0 0 100%;
+    background: green;
+  }
+  .flex > div > div {
+    contain:size;
+    height: 50px;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; height:100px; background:red;">
+  <div style="columns:2; gap:0; column-fill:auto; height:110px;">
+    <div style="height:10px;">
+      <div style="height:50px; background:green;"></div>
+      <div class="flex">
+        <div><div></div></div>
+        <div><div></div></div>
+        <div><div></div></div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-078.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-078.html
new file mode 100644
index 0000000..1427d57
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-078.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1500947">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<style>
+  .flex {
+    display: flex;
+    flex-flow: row wrap;
+  }
+  .flex > div {
+    flex: 0 0 100%;
+    contain: size;
+    height: 50px;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; height:100px; background:red;">
+  <div style="columns:2; gap:0; column-fill:auto; height:120px;">
+    <div style="height:10px;">
+      <div style="height:50px; contain:size; background:green;"></div>
+      <div class="flex">
+        <div></div>
+        <div></div>
+        <div></div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-079.html b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-079.html
new file mode 100644
index 0000000..57c094d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/flexbox/multi-line-row-flex-fragmentation-079.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1500947">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<style>
+  .flex {
+    display: flex;
+    flex-flow: row wrap;
+  }
+  .flex > div {
+    flex: 0 0 100%;
+    contain: size;
+    height: 50px;
+    background: green;
+  }
+</style>
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="width:100px; height:100px; background:red;">
+  <div style="columns:2; gap:0; column-fill:auto; height:120px;">
+    <div style="height:10px;">
+      <div class="flex">
+        <div></div>
+        <div></div>
+        <div></div>
+        <div></div>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/common.js b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/common.js
new file mode 100644
index 0000000..457ae85
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/common.js
@@ -0,0 +1,92 @@
+function checkSnapEventSupport(event_type) {
+  if (event_type == "snapchanged") {
+    assert_true(window.onsnapchanged !== undefined, "snapchanged not supported");
+  } else if (event_type == "snapchanging") {
+    assert_true(window.onsnapchanging !== undefined, "snapchanging not supported");
+  } else {
+    assert_unreached(`Unknown snap event type selected: ${event_type}`);
+  }
+}
+
+function assertSnapEvent(evt, expected_ids) {
+  assert_equals(evt.bubbles, false, "snap events don't bubble");
+  assert_false(evt.cancelable, "snap events are not cancelable.");
+  const actual = Array.from(evt.snapTargets, el => el.id).join(",");
+  const expected = expected_ids.join(",");
+  assert_equals(actual, expected, "snap event supplied expected targets");
+}
+
+// This function holds logic intended to be used by tests for scroll snap
+// events.
+// |test_data| should contain:
+// - |scroller|: the snap container being scrolled (or
+//               document.scrollingElement)
+// - |scrolling_function|: this function should trigger the desired snap event
+//                         when executed.
+// - |expected_snap_targets|: a list of element ids which the triggered snap
+//                            event should supply in SnapEvent.snapTargets.
+// - |expected_scroll_offsets|: the scroll offsets at which the snap container
+//                              should be after scrolling function has been
+//                              executed.
+// |event_type|: should be "snapchanged" or "snapchanging".
+async function test_snap_event(test, test_data, event_type) {
+  checkSnapEventSupport(event_type);
+  await waitForScrollReset(test, test_data.scroller);
+
+  let listener = test_data.scroller ==
+    document.scrollingElement ? document : test_data.scroller;
+
+  const event_promise = waitForSnapEvent(listener, event_type);
+  await test_data.scrolling_function();
+  let evt = await event_promise;
+
+  assertSnapEvent(evt, test_data.expected_snap_targets);
+  assert_approx_equals(test_data.scroller.scrollTop,
+    test_data.expected_scroll_offsets.y, 1,
+    "vertical scroll offset mismatch.");
+  assert_approx_equals(test_data.scroller.scrollLeft,
+    test_data.expected_scroll_offsets.x, 1,
+    "horizontal scroll offset mismatch.");
+}
+
+async function test_snapchanged(test, test_data) {
+  await test_snap_event(test, test_data, "snapchanged");
+}
+
+function waitForEventUntil(event_target, event_type, wait_until) {
+  return new Promise(resolve => {
+    let result = null;
+    const listener = (evt) => {
+      result = evt;
+    };
+    event_target.addEventListener(event_type, listener);
+    wait_until.then(() => {
+      event_target.removeEventListener(event_type, listener);
+      resolve(result);
+    });
+  });
+}
+
+// Proxy a wait for a snap event. We want to avoid having a test
+// timeout in the event of an expected snap event not firing in a particular
+// test case as that would cause the entire file to fail.
+// Snap events should fire before scrollend, so if a scroll should happen, wait
+// for a scrollend event. Otherwise, just do a rAF-based wait.
+function waitForSnapEvent(event_target, event_type, scroll_happens = true) {
+  return scroll_happens ? waitForEventUntil(event_target, event_type,
+                                   waitForScrollendEventNoTimeout(event_target))
+                        : waitForEventUntil(event_target, event_type,
+                                   waitForAnimationFrames(2));
+}
+
+function waitForSnapChangedEvent(event_target, scroll_happens = true) {
+  return waitForSnapEvent(event_target, "snapchanged", scroll_happens);
+}
+
+function getScrollbarToScrollerRatio(scroller) {
+  // Ideally we'd subtract the length of the scrollbar thumb from
+  // the dividend but there isn't currently a way to get the
+  // scrollbar thumb length.
+  return scroller.clientHeight /
+      (scroller.scrollHeight - scroller.clientHeight);
+}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/user-scroll-common.js b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/user-scroll-common.js
new file mode 100644
index 0000000..647f421
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/resources/user-scroll-common.js
@@ -0,0 +1,66 @@
+// Helper functions for snapchanged-on-user-* tests.
+
+// This performs a touch scroll on |scroller| using the coordinates provided
+// in |start_pos| and |end_pos|.
+// It is meant for use in snapchanged & snapchanging tests for triggering snap
+// events when touch scrolling from |start_pos| to |end_pos|.
+function snap_event_touch_scroll_helper(start_pos, end_pos) {
+  return new test_driver.Actions()
+    .addPointer("TestPointer", "touch")
+    .pointerMove(start_pos.x, start_pos.y)
+    .pointerDown()
+    .addTick()
+    .pause(200)
+    .pointerMove(end_pos.x, end_pos.y)
+    .addTick()
+    .pointerUp()
+    .send();
+}
+
+// This drags the provided |scroller|'s scrollbar  vertically by |drag_amt|.
+// Snap event tests should provide a |drag_amt| that would result in a
+// the desired snap event being triggered.
+const vertical_offset_into_scrollbar = 30;
+function snap_event_scrollbar_drag_helper(scroller, scrollbar_width, drag_amt) {
+  let x, y, bounds;
+  if (scroller == document.scrollingElement) {
+    bounds = document.documentElement.getBoundingClientRect();
+    x = window.innerWidth - Math.round(scrollbar_width / 2);
+  } else {
+    bounds = scroller.getBoundingClientRect();
+    x = bounds.right - Math.round(scrollbar_width / 2);
+  }
+  y = bounds.top + vertical_offset_into_scrollbar;
+  return new test_driver.Actions()
+    .addPointer('TestPointer', 'mouse')
+    .pointerMove(x, y)
+    .pointerDown()
+    .pointerMove(x, y + drag_amt)
+    .addTick()
+    .pointerUp()
+    .send();
+}
+
+// This tests that snap event of type |event_type| don't fire for a user (wheel)
+// scroll that snaps back to the same element. Snap events tests should provide
+// a |delta| small enough that no change in |scroller|'s snap targets occurs at
+// the end of the scroll.
+async function test_no_snap_event(test, scroller, delta, event_type) {
+  const listening_element = scroller == document.scrollingElement
+      ? document : scroller;
+  checkSnapEventSupport(event_type);
+  await waitForScrollReset(test, scroller);
+  await waitForCompositorCommit();
+  let snap_event_promise = waitForSnapEvent(listening_element, event_type);
+  // Set the scroll destination to just a little off (0, 0) top so we snap
+  // back to the top box.
+  await new test_driver.Actions().scroll(0, 0, delta, delta).send();
+  let evt = await snap_event_promise;
+  assert_equals(evt, null, "no snap event since scroller is back to top");
+  assert_equals(scroller.scrollTop, 0, "scroller snaps back to the top");
+  assert_equals(scroller.scrollLeft, 0, "scroller snaps back to the left");
+}
+
+async function test_no_snapchanged(t, scroller, delta) {
+  await test_no_snap_event(t, scroller, delta, "snapchanged");
+}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/resources/common.js b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/resources/common.js
deleted file mode 100644
index 3e0fddd..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/resources/common.js
+++ /dev/null
@@ -1,66 +0,0 @@
-function checkSnapchangedSupport() {
-  assert_true(window.onsnapchanged !== undefined, "snapchanged not supported");
-}
-
-function assertSnapchangedEvent(evt, expected_ids) {
-  assert_equals(evt.bubbles, false, "snapchanged event doesn't bubble");
-  assert_false(evt.cancelable, "snapchanged event is not cancelable.");
-  const actual = Array.from(evt.snapTargets, el => el.id).join(",");
-  const expected = expected_ids.join(",");
-  assert_equals(actual, expected, "snapped to expected targets");
-}
-
-async function test_snapchanged(test, test_data) {
-  checkSnapchangedSupport();
-  await waitForScrollReset(test, test_data.scroller);
-
-  let listener = test_data.scroller ==
-      document.scrollingElement ? document : test_data.scroller;
-
-  const snapchanged_promise = waitForSnapChangedEvent(listener);
-  await test_data.scrolling_function();
-  let evt = await snapchanged_promise;
-
-  assertSnapchangedEvent(evt,
-      test_data.expected_snap_targets);
-  assert_approx_equals(test_data.scroller.scrollTop,
-    test_data.expected_scroll_offsets.y, 1,
-    "vertical scroll offset mismatch.");
-  assert_approx_equals(test_data.scroller.scrollLeft,
-    test_data.expected_scroll_offsets.x, 1,
-    "horizontal scroll offset mismatch.");
-}
-
-function waitForEventUntil(event_target, event_type, wait_until) {
-  return new Promise(resolve => {
-    let result = null;
-    const listener = (evt) => {
-      result = evt;
-    };
-    event_target.addEventListener(event_type, listener);
-    wait_until.then(() => {
-      event_target.removeEventListener(event_type, listener);
-      resolve(result);
-    });
-  });
-}
-
-// Proxy a wait for a snapchanged event. We want to avoid having a test
-// timeout in the event of an expected snapchanged not firing in a particular
-// test case as that would cause the entire file to fail.
-// Snapchanged should fire before scrollend, so if a scroll should happen, wait
-// for a scrollend event. Otherwise, just do a rAF-based wait.
-function waitForSnapChangedEvent(event_target, scroll_happens = true) {
-  return scroll_happens ? waitForEventUntil(event_target, "snapchanged",
-                                   waitForScrollendEventNoTimeout(event_target))
-                        : waitForEventUntil(event_target, "snapchanged",
-                                   waitForAnimationFrames(2));
-}
-
-function getScrollbarToScrollerRatio(scroller) {
-  // Ideally we'd subtract the length of the scrollbar thumb from
-  // the dividend but there isn't currently a way to get the
-  // scrollbar thumb length.
-  return scroller.clientHeight /
-      (scroller.scrollHeight - scroller.clientHeight);
-}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/resources/user-scroll-common.js b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/resources/user-scroll-common.js
deleted file mode 100644
index 75f8bf1..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/resources/user-scroll-common.js
+++ /dev/null
@@ -1,62 +0,0 @@
-// Helper functions for snapchanged-on-user-* tests.
-
-// This performs a touch scroll on |scroller| using the coordinates provided
-// in |start_pos| and |end_pos|.
-// It is meant for use in snapchanged tests for triggering snapchanged events
-// when touch scrolling from |start_pos| to |end_pos|.
-function snapchanged_touch_scroll_helper(start_pos, end_pos) {
-  return new test_driver.Actions()
-    .addPointer("TestPointer", "touch")
-    .pointerMove(start_pos.x, start_pos.y)
-    .pointerDown()
-    .addTick()
-    .pause(200)
-    .pointerMove(end_pos.x, end_pos.y)
-    .addTick()
-    .pointerUp()
-    .send();
-}
-
-// This drags the provided |scroller|'s scrollbar  vertically by |drag_amt|.
-// Snapchanged tests should provide a |drag_amt| that would result in a
-// snapchanged event being triggered.
-const vertical_offset_into_scrollbar = 30;
-function snapchanged_scrollbar_drag_helper(scroller, scrollbar_width, drag_amt) {
-  let x, y, bounds;
-  if (scroller == document.scrollingElement) {
-    bounds = document.documentElement.getBoundingClientRect();
-    x = window.innerWidth - Math.round(scrollbar_width / 2);
-  } else {
-    bounds = scroller.getBoundingClientRect();
-    x = bounds.right - Math.round(scrollbar_width / 2);
-  }
-  y = bounds.top + vertical_offset_into_scrollbar;
-  return new test_driver.Actions()
-    .addPointer('TestPointer', 'mouse')
-    .pointerMove(x, y)
-    .pointerDown()
-    .pointerMove(x, y + drag_amt)
-    .addTick()
-    .pointerUp()
-    .send();
-}
-
-// This tests that snapchanged doesn't fire for a user (wheel) scroll that
-// snaps back to the same element. snapchanged tests should provide a |delta|
-// small enough that no change in |scroller|'s snap targets occurs at the end of
-// the scroll.
-async function test_no_snapchanged(test, scroller, delta) {
-  const listening_element = scroller == document.scrollingElement
-      ? document : scroller;
-  checkSnapchangedSupport(test);
-  await waitForScrollReset(test, scroller);
-  await waitForCompositorCommit();
-  let snapchanged_promise = waitForSnapChangedEvent(listening_element);
-  // Set the scroll destination to just a little off (0, 0) top so we snap
-  // back to the top box.
-  await new test_driver.Actions().scroll(0, 0, delta, delta).send();
-  let evt = await snapchanged_promise;
-  assert_equals(evt, null, "no snapchanged since scroller is back to top");
-  assert_equals(scroller.scrollTop, 0, "scroller snaps back to the top");
-  assert_equals(scroller.scrollLeft, 0, "scroller snaps back to the left");
-}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-after-layout-change.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-after-layout-change.tentative.html
index 193c907..293400ed 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-after-layout-change.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-after-layout-change.tentative.html
@@ -6,7 +6,7 @@
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
   <script src="/dom/events/scrolling/scroll_support.js"></script>
-  <script src="resources/common.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/common.js"></script>
   <script src="/web-animations/testcommon.js"></script>
 </head>
 
@@ -56,7 +56,7 @@
     }
 
     async function setup(t) {
-      checkSnapchangedSupport(t);
+      checkSnapEventSupport("snapchanged");
       await reset(t);
       await waitForCompositorCommit();
       assert_equals(scroller.scrollTop, 0, "test precondition: scroller " +
@@ -81,7 +81,7 @@
       inner_snap_area.style.height =
         `${scroller.clientHeight + inner_snap_area.clientHeight - 10}px`;
       const evt = await snapchanged_promise;
-      assertSnapchangedEvent(evt, [outer_snap_area.id, inner_snap_area.id]);
+      assertSnapEvent(evt, [outer_snap_area.id, inner_snap_area.id]);
       target_snap_position = inner_snap_area.offsetTop +
           inner_snap_area.offsetHeight - scroller.clientHeight;
       assert_equals(scroller.scrollTop, target_snap_position,
@@ -107,7 +107,7 @@
       inner_snap_area.style.height =
           `${scroller.clientHeight + inner_snap_area.clientHeight + 10}px`;
       const evt = await snapchanged_promise;
-      assertSnapchangedEvent(evt, [outer_snap_area.id, inner_snap_area.id]);
+      assertSnapEvent(evt, [outer_snap_area.id, inner_snap_area.id]);
       assert_equals(scroller.scrollTop, target_snap_position,
           "scroller maintains offset which is now covering within inner area");
     }, "snapchanged fires after snap area is snapped to upon layout change " +
@@ -119,11 +119,11 @@
       let snapchanged_promise = waitForSnapChangedEvent(scroller, false);
       scroller.style.scrollSnapType = "none";
       let evt = await snapchanged_promise;
-      assertSnapchangedEvent(evt, []);
+      assertSnapEvent(evt, []);
       snapchanged_promise = waitForSnapChangedEvent(scroller, false);
       scroller.style.scrollSnapType = "y mandatory";
       evt = await snapchanged_promise;
-      assertSnapchangedEvent(evt, [outer_snap_area.id]);
+      assertSnapEvent(evt, [outer_snap_area.id]);
     }, "snapchanged fires when container stops snapping");
 
     promise_test(async(t) => {
@@ -133,12 +133,12 @@
       inner_snap_area.style.scrollSnapAlign = "none";
       outer_snap_area.style.scrollSnapAlign = "none";
       let evt = await snapchanged_promise;
-      assertSnapchangedEvent(evt, []);
+      assertSnapEvent(evt, []);
       snapchanged_promise = waitForSnapChangedEvent(scroller, false);
       inner_snap_area.style.scrollSnapAlign = "start";
       outer_snap_area.style.scrollSnapAlign = "start";
       evt = await snapchanged_promise;
-      assertSnapchangedEvent(evt, [outer_snap_area.id]);
+      assertSnapEvent(evt, [outer_snap_area.id]);
     }, "snapchanged fires when snap container no longer has snap areas");
   </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-ensures-dom-order.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-ensures-dom-order.html
index fc7abe9..10bc73b6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-ensures-dom-order.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-ensures-dom-order.html
@@ -6,7 +6,7 @@
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
   <script src="/dom/events/scrolling/scroll_support.js"></script>
-  <script src="resources/common.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/common.js"></script>
   <script src="/web-animations/testcommon.js"></script>
   <style>
     #scroller {
@@ -62,7 +62,7 @@
   </div>
   <script>
     promise_test(async (t) => {
-      checkSnapchangedSupport();
+      checkSnapEventSupport("snapchanged");
       await waitForCompositorCommit();
       const snapchanged_promise = waitForSnapChangedEvent(scroller, false);
       const snap_point_3 = document.createElement("div");
@@ -72,12 +72,12 @@
       });
       scroller.insertBefore(snap_point_3, snap_point_2);
       const evt_seen = await snapchanged_promise;
-      assertSnapchangedEvent(evt_seen,
+      assertSnapEvent(evt_seen,
                       [snap_point_1.id, snap_point_3.id, snap_point_2.id]);
     }, "snapchanged lists snapTargets in DOM order.");
 
     promise_test(async (t) => {
-      checkSnapchangedSupport();
+      checkSnapEventSupport("snapchanged");
       await waitForCompositorCommit();
       const unreached_func = t.unreached_func("snapchanged shouldn't fire " +
           "since the scroller is snapped to the same elements despite the " +
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-root-scroll.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-root-scroll.tentative.html
index 24c7aa5..d7cc409 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-root-scroll.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-root-scroll.tentative.html
@@ -7,7 +7,7 @@
   <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events">
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
-  <script src="resources/common.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/common.js"></script>
   <script src="/dom/events/scrolling/scroll_support.js"></script>
   <script src="/web-animations/testcommon.js"></script>
 </head>
@@ -79,7 +79,7 @@
     }, "snapchanged event fires after snap target changes via scrollTo");
 
     promise_test(async (t) => {
-      checkSnapchangedSupport();
+      checkSnapEventSupport("snapchanged");
       await waitForScrollReset(t, scroller);
       await waitForCompositorCommit();
       assert_equals(scroller.scrollTop, 0,
@@ -110,7 +110,7 @@
       scroller.scrollTo(scroll_left_target, scroll_top_target);
 
       evt = await snapchanged_promise;
-      assertSnapchangedEvent(evt, [snap_point_2.id]);
+      assertSnapEvent(evt, [snap_point_2.id]);
       assert_equals(scroller.scrollTop, snap_point_2.offsetTop,
         "scroller snaps to the top of snap_point_2");
       assert_equals(scroller.scrollLeft, snap_point_2.offsetLeft,
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-scroll.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-scroll.tentative.html
index d3f7b368..eebe057 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-scroll.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-programmatic-scroll.tentative.html
@@ -7,7 +7,7 @@
   <link rel="help" href="https://drafts.csswg.org/css-scroll-snap-2/#snap-events">
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
-  <script src="resources/common.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/common.js"></script>
   <script src="/dom/events/scrolling/scroll_support.js"></script>
   <script src="/web-animations/testcommon.js"></script>
 </head>
@@ -86,7 +86,7 @@
     }, "snapchanged event fires after snap target changes via scrollTo");
 
     promise_test(async (t) => {
-      checkSnapchangedSupport();
+      checkSnapEventSupport("snapchanged");
       await waitForScrollReset(t, scroller);
       await waitForCompositorCommit();
       assert_equals(scroller.scrollTop, 0,
@@ -115,7 +115,7 @@
       scroller.scrollTo(scroll_left_target, scroll_top_target);
 
       evt = await snapchanged_promise;
-      assertSnapchangedEvent(evt, [snap_point_2.id]);
+      assertSnapEvent(evt, [snap_point_2.id]);
       assert_equals(scroller.scrollTop, snap_point_2.offsetTop,
           "scroller snaps to the top of snap_point_2");
       assert_equals(scroller.scrollLeft, snap_point_2.offsetLeft,
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-root-scroll.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-root-scroll.tentative.html
index 8ea895c..5405d778 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-root-scroll.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-root-scroll.tentative.html
@@ -10,8 +10,8 @@
   <script src="/resources/testdriver.js"></script>
   <script src="/resources/testdriver-actions.js"></script>
   <script src="/resources/testdriver-vendor.js"></script>
-  <script src="resources/common.js"></script>
-  <script src="resources/user-scroll-common.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/user-scroll-common.js"></script>
   <script src="/dom/events/scrolling/scroll_support.js"></script>
   <script src="/web-animations/testcommon.js"></script>
 </head>
@@ -80,7 +80,7 @@
       const test_data = {
         scroller: scroller,
         scrolling_function: async () => {
-          await snapchanged_touch_scroll_helper(start_pos, end_pos);
+          await snap_event_touch_scroll_helper(start_pos, end_pos);
         },
         expected_snap_targets: [snap_point_2.id],
         expected_scroll_offsets: {
@@ -129,7 +129,7 @@
           // Scroll by just over half of the top box's height.
           const drag_amt = (offset_to_snap_point_2.y / 2 + 1) *
               scrollbar_to_scroller_ratio;
-          await snapchanged_scrollbar_drag_helper(scroller, scrollbar_width,
+          await snap_event_scrollbar_drag_helper(scroller, scrollbar_width,
                                                   drag_amt);
         },
         expected_snap_targets: [snap_point_1.id, snap_point_2.id],
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-scroll.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-scroll.tentative.html
index 8468aa5..4f36200 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-scroll.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-on-user-scroll.tentative.html
@@ -10,8 +10,8 @@
   <script src="/resources/testdriver.js"></script>
   <script src="/resources/testdriver-actions.js"></script>
   <script src="/resources/testdriver-vendor.js"></script>
-  <script src="resources/common.js"></script>
-  <script src="resources/user-scroll-common.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/common.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/user-scroll-common.js"></script>
   <script src="/dom/events/scrolling/scroll_support.js"></script>
   <script src="/web-animations/testcommon.js"></script>
 </head>
@@ -87,7 +87,7 @@
       const test_data = {
         scroller: scroller,
         scrolling_function: async () => {
-          await snapchanged_touch_scroll_helper(start_pos, end_pos);
+          await snap_event_touch_scroll_helper(start_pos, end_pos);
         },
         expected_snap_targets: [snap_point_2.id],
         expected_scroll_offsets: {
@@ -134,7 +134,7 @@
           // Scroll by just over half of the top box's height.
           const drag_amt = (offset_to_snap_point_2.y / 2 + 1) *
               scrollbar_to_scroller_ratio;
-          await snapchanged_scrollbar_drag_helper(scroller, scrollbar_width, drag_amt);
+          await snap_event_scrollbar_drag_helper(scroller, scrollbar_width, drag_amt);
         },
         expected_snap_targets: [snap_point_1.id, snap_point_2.id],
         expected_scroll_offsets: {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-same-targets-after-layout-changed.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-same-targets-after-layout-changed.html
index e41e5629..4d16bd80 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-same-targets-after-layout-changed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-same-targets-after-layout-changed.html
@@ -6,7 +6,7 @@
   <script src="/resources/testharness.js"></script>
   <script src="/resources/testharnessreport.js"></script>
   <script src="/dom/events/scrolling/scroll_support.js"></script>
-  <script src="resources/common.js"></script>
+  <script src="/css/css-scroll-snap-2/resources/common.js"></script>
   <style>
     #scroller {
       overflow-y: scroll;
@@ -61,7 +61,7 @@
   <script>
     let unreached_func = null;
     promise_test(async (t) => {
-      checkSnapchangedSupport();
+      checkSnapEventSupport("snapchanged");
       await waitForCompositorCommit();
       unreached_func = t.unreached_func("snapchanged shouldn't fire " +
           "since the scroller is snapped to the same elements.");
@@ -82,7 +82,7 @@
     "elements");
 
     promise_test(async (t) => {
-      checkSnapchangedSupport();
+      checkSnapEventSupport("snapchanged");
       await waitForCompositorCommit();
       unreached_func = t.unreached_func("snapchanged shouldn't fire " +
           "since the scroller is snapped to the same elements.");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-with-proximity-strictness.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-with-proximity-strictness.tentative.html
index 66c5222c..cb55054 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-with-proximity-strictness.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-scroll-snap-2/snapchanged/snapchanged-with-proximity-strictness.tentative.html
@@ -8,7 +8,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/dom/events/scrolling/scroll_support.js"></script>
-<script src="resources/common.js"></script>
+<script src="/css/css-scroll-snap-2/resources/common.js"></script>
 <style>
   div {
     position: absolute;
@@ -46,7 +46,7 @@
   let resolve_func = null;
 
   promise_test(async (test) => {
-    checkSnapchangedSupport();
+    checkSnapEventSupport("snapchanged");
     await waitForCompositorCommit();
     // The initial snap position is at (0, 0).
     assert_equals(scroller.scrollTop, 0);
@@ -61,7 +61,7 @@
     // to outside the proximity range and are no longer snapped.
     let evt = await snapchanged_promise;
     assert_equals(scroller.scrollTop, 250);
-    assertSnapchangedEvent(evt, []);
+    assertSnapEvent(evt, []);
     evt = null;
 
     snapchanged_promise = waitForSnapChangedEvent(scroller);
@@ -73,7 +73,7 @@
     assert_equals(scroller.scrollTop, 0);
     // snapchanged should fire as we've moved from outside the proximity range
     // to inside the proximity range and are once again snapped.
-    assertSnapchangedEvent(evt, [target.id]);
+    assertSnapEvent(evt, [target.id]);
   }, "Snapchanged fires when scrolling outside proximity range.");
   </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-002-ref.html
new file mode 100644
index 0000000..f3b5554
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-002-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Helmut Januschka" href="mailto:helmut@januschka.com" />
+</head>
+
+<body>
+  <div  style="margin: 10px; height: 50px; width: 100px; outline: green auto;">green outline</div>
+  <div  style="margin: 10px; height: 50px; width: 100px; outline: green auto;" >green outline</div>
+  <div  style="margin: 10px; height: 50px; width: 100px; outline: purple auto;" >purple outline</div>
+  <div  style="margin: 10px; height: 50px; width: 100px; outline: green solid; " >green outline</div>
+</body>
+
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-002.html b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-002.html
new file mode 100644
index 0000000..f23ef69
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-002.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Basic User Interface Test: outline-color - respect accent-color </title>
+<link rel="author" title="Helmut Januschka" href="mailto:helmut@januschka.com">
+<link rel="help" title="'outline-color' should use 'accent-color' if auto"
+  href="https://www.w3.org/TR/css-ui-4/#propdef-outline-color">
+<link rel="match" href="outline-color-002-ref.html">
+<meta name="assert" content="Test Checks if accent-color is used when outline-color and outline-style auto">
+
+<body>
+    <div  style="margin: 10px; height: 50px; width: 100px; outline: green auto">green outline</div>
+    <div  style="margin: 10px; height: 50px; width: 100px; outline: green auto; accent-color: purple;" >green outline</div>
+    <div  style="margin: 10px; height: 50px; width: 100px; outline: auto; accent-color: purple;" >purple outline</div>
+    <div  style="margin: 10px; height: 50px; width: 100px; outline: green solid; " >green outline</div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-003-ref.html b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-003-ref.html
new file mode 100644
index 0000000..78557c7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-003-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Helmut Januschka" href="mailto:helmut@januschka.com" />
+</head>
+
+<body>
+  <div style="padding: 10px; width: 200px; height: 50px; outline: purple auto;">
+    <div style="outline: green auto;">Green inner outline with a purple outer outline</div>
+  </div>
+</body>
+
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-003.html b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-003.html
new file mode 100644
index 0000000..b2c9e813
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-003.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Basic User Interface Test: outline-color - respect accent-color </title>
+<link rel="author" title="Helmut Januschka" href="mailto:helmut@januschka.com">
+<link rel="help" title="'outline-color' should use 'accent-color' if auto"
+  href="https://www.w3.org/TR/css-ui-4/#propdef-outline-color">
+<link rel="match" href="outline-color-003-ref.html">
+<meta name="assert" content="Test Checks if accent-color is used when outline-color and outline-style auto">
+
+<div style="padding: 10px; width: 200px; height: 50px; outline: auto; accent-color: purple;">
+  <div style="outline: inherit; accent-color: green;">Green inner outline with a purple outer outline</div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-004-ref.html b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-004-ref.html
new file mode 100644
index 0000000..5da6b388
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-004-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>CSS Reftest Reference</title>
+  <link rel="author" title="Helmut Januschka" href="mailto:helmut@januschka.com" />
+</head>
+
+<body>
+  <div style="padding: 10px; width: 200px; height: 50px; outline-style: solid; outline-color: purple;">
+    <div style="outline-style: auto; outline-color: green; color: red">Green inner outline with a
+      purple outer outline</div>
+  </div>
+
+</body>
+
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-004.html b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-004.html
new file mode 100644
index 0000000..3bd1ea4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/outline-color-004.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Basic User Interface Test: outline-color - respect accent-color </title>
+<link rel="author" title="Helmut Januschka" href="mailto:helmut@januschka.com">
+<link rel="help" title="'outline-color' should use 'accent-color' if auto"
+  href="https://www.w3.org/TR/css-ui-4/#propdef-outline-color">
+<link rel="match" href="outline-color-004-ref.html">
+<meta name="assert" content="Test Checks if accent-color is used when outline-color and outline-style auto">
+
+<div style="padding: 10px; width: 200px; height: 50px; outline-style: solid; outline-color: auto; accent-color: red; color: purple;">
+  <div style="outline-style: auto; outline-color: inherit; accent-color: green; color: red;">Green inner outline with a
+    purple outer outline</div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any-expected.txt b/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any-expected.txt
new file mode 100644
index 0000000..18ed5da
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any-expected.txt
@@ -0,0 +1,26 @@
+This is a testharness.js-based test.
+[PASS] Observable constructor
+[PASS] Subscriber interface is not constructible
+[PASS] subscribe() can be called with no arguments
+[PASS] Observable constructor calls initializer on subscribe
+[PASS] Observable error path called synchronously
+[PASS] Observable should error if initializer throws
+[PASS] Subscription is inactive after complete()
+[PASS] Subscription is inactive after error()
+[FAIL] Subscription is inactive when aborted signal is passed in
+  assert_false: expected false got true
+[PASS] Subscriber#signal is not the same AbortSignal as the one passed into `subscribe()`
+[PASS] Subscription does not emit values after completion
+[PASS] Subscription does not emit values after error
+[PASS] Completing or nexting a subscriber after an error does nothing
+[PASS] Errors pushed to the subscriber that are not handled by the subscription are reported to the global
+[PASS] Errors thrown in the initializer that are not handled by the subscription are reported to the global
+[PASS] Subscription reports errors that are pushed after subscriber is closed by completion
+[PASS] Errors thrown by initializer function after subscriber is closed by completion are reported
+[PASS] Errors thrown by initializer function after subscriber is closed by error are reported
+[PASS] Errors pushed by initializer function after subscriber is closed by error are reported
+[PASS] Subscriber#complete() cannot re-entrantly invoke itself
+[PASS] Subscriber#error() cannot re-entrantly invoke itself
+[PASS] Unsubscription lifecycle
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any.js b/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any.js
index bda9a56..2499531 100644
--- a/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any.js
+++ b/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any.js
@@ -101,27 +101,38 @@
   const error = new Error("error");
   const results = [];
   let errorReported = null;
+  let innerSubscriber = null;
+  let subscriptionActivityInFinallyAfterThrow;
+  let subscriptionActivityInErrorHandlerAfterThrow;
 
   self.addEventListener("error", e => errorReported = e, {once: true});
 
   const source = new Observable((subscriber) => {
+    innerSubscriber = subscriber;
     subscriber.next(1);
-    throw error;
-    // TODO(https://github.com/WICG/observable/issues/76): If we add the
-    // `subscriber.closed` attribute, consider a try-finally block to assert
-    // that `subscriber.closed` is true after throwing. Also TODO: ensure that
-    // that would even be the right behavior.
+    try {
+      throw error;
+    } finally {
+      subscriptionActivityInFinallyAfterThrow = subscriber.active;
+    }
   });
 
   source.subscribe({
     next: (x) => results.push(x),
-    error: (e) => results.push(e),
+    error: (e) => {
+      subscriptionActivityInErrorHandlerAfterThrow = innerSubscriber.active;
+      results.push(e);
+    },
     complete: () => assert_unreached("complete should not be called"),
   });
 
   assert_equals(errorReported, null, "The global error handler should not be " +
       "invoked when the subscribe callback throws an error and the " +
       "subscriber has given an error handler");
+  assert_true(subscriptionActivityInFinallyAfterThrow, "Subscriber is " +
+      "considered active in finally block before error handler is invoked");
+  assert_false(subscriptionActivityInErrorHandlerAfterThrow, "Subscriber is " +
+      "considered inactive in error handler block after thrown error");
   assert_array_equals(
     results,
     [1, error],
@@ -129,14 +140,64 @@
   );
 }, "Observable should error if initializer throws");
 
-// TODO(https://github.com/WICG/observable/issues/76): If we decide the
-// `subscriber.closed` attribute is needed, re-visit these two tests that were
-// originally included:
-// https://github.com/web-platform-tests/wpt/blob/0246526ca46ef4e5eae8b8e4a87dd905c40f5326/dom/observable/tentative/observable-ctor.any.js#L123-L137.
+test(t => {
+  let innerSubscriber = null;
+  let activeAfterComplete = false;
+  let activeDuringComplete = false;
 
-// TODO(domfarolino): Add a test asserting that `Subscriber#signal` != the
-// actual `AbortSignal` passed into `subscribe()`. See
-// https://github.com/web-platform-tests/wpt/pull/42219#discussion_r1361243283.
+  const source = new Observable((subscriber) => {
+    innerSubscriber = subscriber;
+
+    subscriber.complete();
+    activeAfterComplete = subscriber.active;
+  });
+
+  source.subscribe({complete: () => activeDuringComplete = innerSubscriber.active});
+  assert_false(activeDuringComplete, "Subscription is not active during complete");
+  assert_false(activeAfterComplete, "Subscription is not active after complete");
+}, "Subscription is inactive after complete()");
+
+test(t => {
+  let innerSubscriber = null;
+  let activeAfterError = false;
+  let activeDuringError = false;
+
+  const error = new Error("error");
+  const source = new Observable((subscriber) => {
+    innerSubscriber = subscriber;
+
+    subscriber.error(error);
+    activeAfterError = subscriber.active;
+  });
+
+  source.subscribe({error: () => activeDuringError = innerSubscriber.active});
+  assert_false(activeDuringError, "Subscription is not active during error");
+  assert_false(activeAfterError, "Subscription is not active after error");
+}, "Subscription is inactive after error()");
+
+test(t => {
+  let initialActivity;
+
+  const source = new Observable((subscriber) => {
+    initialActivity = subscriber.active;
+  });
+
+  const ac = new AbortController();
+  ac.abort();
+  source.subscribe({signal: ac.signal});
+  assert_false(initialActivity);
+}, "Subscription is inactive when aborted signal is passed in");
+
+test(() => {
+  let outerSubscriber = null;
+
+  const source = new Observable(subscriber => outerSubscriber = subscriber);
+
+  const controller = new AbortController();
+  source.subscribe({signal: controller.signal});
+
+  assert_not_equals(controller.signal, outerSubscriber.signal);
+}, "Subscriber#signal is not the same AbortSignal as the one passed into `subscribe()`");
 
 test(() => {
   const results = [];
@@ -459,3 +520,45 @@
     "error() can only be called once, and cannot invoke other Observer methods"
   );
 }, "Subscriber#error() cannot re-entrantly invoke itself");
+
+// TODO(domfarolino): Once `Subscriber#addTeardown()` and `Subscriber#active`
+// are implemented, add corresponding code for them here so we can assert the following order of everything:
+//   1. The passed-in `Observer#signal` is marked as `aborted`
+//   2. Abort event handlers are invoked for the that outer, passed-in signal.
+//   3. `Subscriber#closed` is true
+//   4. `Subscriber#signal` is marked as aborted
+//   5. Teardown callbacks are executed in the right order
+//   6. Abort event handlers are invoked for `Subscriber#signal`.
+// This ensures we have the "dependent signal" logic wired up correctly:
+// https://dom.spec.whatwg.org/#create-a-dependent-abort-signal.
+test(() => {
+  const results = [];
+  let innerSubscriber = null;
+
+  const source = new Observable((subscriber) => {
+    results.push('subscribe() callback');
+    innerSubscriber = subscriber;
+
+    subscriber.signal.addEventListener('abort', () => {
+      assert_true(subscriber.signal.aborted);
+      subscriber.next('inner abort handler');
+    });
+  });
+
+  const ac = new AbortController();
+  source.subscribe({
+    next: (x) => results.push(x),
+    signal: ac.signal,
+  });
+
+  ac.signal.addEventListener('abort', () => {
+    results.push('outer abort handler');
+    assert_true(ac.signal.aborted);
+    assert_false(innerSubscriber.signal.aborted);
+  });
+
+  assert_array_equals(results, ['subscribe() callback']);
+  ac.abort();
+  assert_array_equals(results, ['subscribe() callback',
+      'outer abort handler', 'inner abort handler']);
+}, "Unsubscription lifecycle");
diff --git a/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any.worker-expected.txt
new file mode 100644
index 0000000..18ed5da
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/dom/observable/tentative/observable-constructor.any.worker-expected.txt
@@ -0,0 +1,26 @@
+This is a testharness.js-based test.
+[PASS] Observable constructor
+[PASS] Subscriber interface is not constructible
+[PASS] subscribe() can be called with no arguments
+[PASS] Observable constructor calls initializer on subscribe
+[PASS] Observable error path called synchronously
+[PASS] Observable should error if initializer throws
+[PASS] Subscription is inactive after complete()
+[PASS] Subscription is inactive after error()
+[FAIL] Subscription is inactive when aborted signal is passed in
+  assert_false: expected false got true
+[PASS] Subscriber#signal is not the same AbortSignal as the one passed into `subscribe()`
+[PASS] Subscription does not emit values after completion
+[PASS] Subscription does not emit values after error
+[PASS] Completing or nexting a subscriber after an error does nothing
+[PASS] Errors pushed to the subscriber that are not handled by the subscription are reported to the global
+[PASS] Errors thrown in the initializer that are not handled by the subscription are reported to the global
+[PASS] Subscription reports errors that are pushed after subscriber is closed by completion
+[PASS] Errors thrown by initializer function after subscriber is closed by completion are reported
+[PASS] Errors thrown by initializer function after subscriber is closed by error are reported
+[PASS] Errors pushed by initializer function after subscriber is closed by error are reported
+[PASS] Subscriber#complete() cannot re-entrantly invoke itself
+[PASS] Subscriber#error() cannot re-entrantly invoke itself
+[PASS] Unsubscription lifecycle
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/event-timing/resources/event-timing-test-utils.js b/third_party/blink/web_tests/external/wpt/event-timing/resources/event-timing-test-utils.js
index 66aa05a..ddbaafb 100644
--- a/third_party/blink/web_tests/external/wpt/event-timing/resources/event-timing-test-utils.js
+++ b/third_party/blink/web_tests/external/wpt/event-timing/resources/event-timing-test-utils.js
@@ -3,15 +3,15 @@
 // event handler if |callback| is provided.
 async function clickOnElementAndDelay(id, delay, callback) {
   const element = document.getElementById(id);
-  const clickHandler = () => {
+  const pointerdownHandler = () => {
     mainThreadBusy(delay);
     if (callback) {
       callback();
     }
-    element.removeEventListener("pointerdown", clickHandler);
+    element.removeEventListener("pointerdown", pointerdownHandler);
   };
 
-  element.addEventListener("pointerdown", clickHandler);
+  element.addEventListener("pointerdown", pointerdownHandler);
   await test_driver.click(element);
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/layout-instability/recent-input.html b/third_party/blink/web_tests/external/wpt/layout-instability/recent-input.html
index 2779d4ff..1aa9dfe 100644
--- a/third_party/blink/web_tests/external/wpt/layout-instability/recent-input.html
+++ b/third_party/blink/web_tests/external/wpt/layout-instability/recent-input.html
@@ -25,36 +25,42 @@
   assert_implements(window.LayoutShift, 'Layout Instability is not supported.');
   // Wait for the initial render to complete.
   await waitForAnimationFrames(2);
-
   const startTime = performance.now();
-  return new Promise(resolve => {
+
+  const observerPromise = new Promise(resolve => {
     const observer = new PerformanceObserver(
-      t.step_func(entryList => {
-        const endTime = performance.now();
-        assert_equals(entryList.getEntries().length, 1);
-        const entry = entryList.getEntries()[0];
-        assert_equals(entry.entryType, "layout-shift");
-        assert_equals(entry.name, "");
-        assert_greater_than_equal(entry.startTime, startTime);
-        assert_less_than_equal(entry.startTime, endTime);
-        assert_equals(entry.duration, 0.0);
-        // The layout shift value should be:
-        // 300 * (100 + 60) * (60 / maxDimension) / viewport size.
-        assert_equals(entry.value, computeExpectedScore(300 * (100 + 60), 60));
-        // We should see that there was a click input entry.
-        assert_equals(entry.hadRecentInput, true);
-        assert_greater_than_equal(timeAfterClick, entry.lastInputTime);
-        resolve();
-      })
-    );
-    observer.observe({entryTypes: ['layout-shift']});
-    // User input event
-    clickAndBlockMain('button').then(() => {
-      timeAfterClick = performance.now();
-      // Modify the position of the div.
-      document.getElementById('myDiv').style = "top: 60px";
-    });
+      entryList => {
+        resolve(entryList);
+      }
+    ).observe({ entryTypes: ['layout-shift'] });
   });
+
+  // User input event
+  await clickAndBlockMain('button');
+
+  // Modify the position of the div to trigger layout shift.
+  document.getElementById('myDiv').style = "top: 60px";
+
+  const layoutShiftEntryList = await observerPromise;
+  const endTime = performance.now();
+
+  assert_equals(layoutShiftEntryList.getEntries().length, 1);
+  const entry = layoutShiftEntryList.getEntries()[0];
+  assert_equals(entry.entryType, "layout-shift");
+  assert_equals(entry.name, "");
+  assert_greater_than(entry.startTime, startTime,
+    "The layout shift entry startTime should be greater than the test startTime.");
+  assert_less_than(entry.startTime, endTime,
+    "The layout shift entry startTime should be less than the test endTime.");
+  assert_equals(entry.duration, 0.0);
+  // The layout shift value should be:
+  // 300 * (100 + 60) * (60 / maxDimension) / viewport size.
+  assert_equals(entry.value, computeExpectedScore(300 * (100 + 60), 60));
+  // We should see that there was a click input entry.
+  assert_equals(entry.hadRecentInput, true);
+  assert_less_than(entry.lastInputTime, entry.startTime,
+    "The lastInputTime should be less than the layout shift startTime.");
+
 }, 'Layout shift right after user input is observable via PerformanceObserver.');
 </script>
 
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 092def7c..1f45be86 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -1413,6 +1413,7 @@
     setter onquotachange
 interface Subscriber
     attribute @@toStringTag
+    getter active
     getter signal
     method complete
     method constructor
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/fetch/api/redirect/redirect-keepalive.https.any-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/fetch/api/redirect/redirect-keepalive.https.any-expected.txt
new file mode 100644
index 0000000..2e0a803
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/fetch/api/redirect/redirect-keepalive.https.any-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+Harness Error. harness_status.status = 1 , harness_status.message = Uncaught SyntaxError: Identifier 'MessageQueue' has already been declared
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/browsing-topics/README.md b/third_party/blink/web_tests/virtual/browsing-topics/README.md
index 90db1997..d423d31 100644
--- a/third_party/blink/web_tests/virtual/browsing-topics/README.md
+++ b/third_party/blink/web_tests/virtual/browsing-topics/README.md
@@ -1,6 +1,6 @@
 # Topics API
 
-The tests are run with the flag --enable-features=BrowsingTopics,BrowsingTopicsXHR,PrivacySandboxAdsAPIsOverride
+The tests are run with the flag --enable-features=BrowsingTopics,PrivacySandboxAdsAPIsOverride
 
 You can run these tests by targeting the following directory:
 `virtual/browsing-topics/external/wpt/browsing-topics`
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 1a7beec2..c7ee70f 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -1937,6 +1937,7 @@
 [Worker]     setter onquotachange
 [Worker] interface Subscriber
 [Worker]     attribute @@toStringTag
+[Worker]     getter active
 [Worker]     getter signal
 [Worker]     method complete
 [Worker]     method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 6031e80..d521855 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -9496,6 +9496,7 @@
     method constructor
 interface Subscriber
     attribute @@toStringTag
+    getter active
     getter signal
     method complete
     method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
index 45a131a..e9eb9aa 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -1329,6 +1329,7 @@
 [Worker]     setter onquotachange
 [Worker] interface Subscriber
 [Worker]     attribute @@toStringTag
+[Worker]     getter active
 [Worker]     getter signal
 [Worker]     method complete
 [Worker]     method constructor
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index cc2be1a..bbcb561 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit cc2be1ac1c1ed9a68d7d151025dfa1d0c8a378b4
+Subproject commit bbcb5613de7c77e7bc70b77b0849b238ce472f29
diff --git a/third_party/dawn b/third_party/dawn
index 46b9ded..235d4e5 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 46b9ded3a891f6c7597aa9ea6647628541c61fff
+Subproject commit 235d4e59cb461ae67751d591d10315e427b0a934
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 7a9b709..b0cfbe5 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 7a9b709a6c649b3900f4eed0e855ef1541dbd642
+Subproject commit b0cfbe504be43b05745de4ddff41ffba7feea706
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 733813f..7622fc04 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 733813f828425537fcbc51db93716e4e7033343c
+Subproject commit 7622fc040bfb73ebf8f196260dbe70d725310f33
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index a5651fc..bac5f49 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit a5651fc48b0a1fedb93779da685535c11100bd31
+Subproject commit bac5f4964ab90c18db43d5ab2bcbc427f7227ad4
diff --git a/third_party/distributed_point_functions/fuzz/dpf_fuzzer.cc b/third_party/distributed_point_functions/fuzz/dpf_fuzzer.cc
index d383024..8589935 100644
--- a/third_party/distributed_point_functions/fuzz/dpf_fuzzer.cc
+++ b/third_party/distributed_point_functions/fuzz/dpf_fuzzer.cc
@@ -78,11 +78,20 @@
                                       evaluation_points[i], parameters);
   }
 
+  // Evaluating a key with N correction words leads to an O(2^N) malloc, which
+  // will unsurprisingly cause a fuzzer crash. See <https://crbug.com/1494260>.
+  constexpr size_t kMaxCorrectionWords = 30;
+  if (ctx0.key().correction_words().size() > kMaxCorrectionWords) {
+    return;
+  }
   absl::StatusOr<std::vector<T>> result_0 =
       dpf.EvaluateUntil<T>(hierarchy_level, prefixes, ctx0);
+  DPF_FUZZER_ASSERT(result_0.ok());
+  if (ctx1.key().correction_words().size() > kMaxCorrectionWords) {
+    return;
+  }
   absl::StatusOr<std::vector<T>> result_1 =
       dpf.EvaluateUntil<T>(hierarchy_level, prefixes, ctx1);
-  DPF_FUZZER_ASSERT(result_0.ok());
   DPF_FUZZER_ASSERT(result_1.ok());
 
   DPF_FUZZER_ASSERT(result_0->size() == result_1->size());
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index b33d972c..14b2dc5 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-13-2-67-g0f98994ef
-Revision: 0f98994ef6f71596890d98444f70b448c9bf0bdc
+Version: VER-2-13-2-68-g667aad581
+Revision: 667aad581a0419d2180087ee1c7e7d7dfac8462f
 CPEPrefix: cpe:/a:freetype:freetype:2.13.2
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/third_party/freetype/src b/third_party/freetype/src
index 0f98994..667aad5 160000
--- a/third_party/freetype/src
+++ b/third_party/freetype/src
@@ -1 +1 @@
-Subproject commit 0f98994ef6f71596890d98444f70b448c9bf0bdc
+Subproject commit 667aad581a0419d2180087ee1c7e7d7dfac8462f
diff --git a/third_party/ml b/third_party/ml
index 982c7b8..4b4be93 160000
--- a/third_party/ml
+++ b/third_party/ml
@@ -1 +1 @@
-Subproject commit 982c7b896a65d1c09fa1720bf29c104b108b4621
+Subproject commit 4b4be935e1100edfb078cfff61b39063a09e90a3
diff --git a/third_party/skia b/third_party/skia
index b7d5819..5fbacd5 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit b7d581997f2b1cebb8f8361f84c2eab8162b2836
+Subproject commit 5fbacd5d68a5e722eb516ed91301d894f57ac00c
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 144310a..bdc4e76c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -1492,6 +1492,8 @@
   <int value="12" label="Web Search Bar (Camera)"/>
   <int value="13" label="Companion (Region)"/>
   <int value="14" label="Translate Onebox (Camera)"/>
+  <int value="15" label="iOS Shortcuts App (Camera)"/>
+  <int value="16" label="Web Images Search Bar (Camera)"/>
 </enum>
 
 <enum name="AmbientUiMode">
@@ -8204,6 +8206,8 @@
   <int value="7" label="Plus Button"/>
   <int value="8" label="Web Search Bar"/>
   <int value="9" label="Translate Onebox"/>
+  <int value="10" label="iOS Shortcuts App"/>
+  <int value="11" label="Web Images Search Bar"/>
 </enum>
 
 <enum name="CameraPreviewSnapPosition">
@@ -8226,6 +8230,15 @@
   <int value="3" label="Dismissed"/>
 </enum>
 
+<enum name="CampaignsManagerError">
+  <int value="0" label="Loading campaigns component failed"/>
+  <int value="1" label="Loading campaigns file failed"/>
+  <int value="2" label="Parsing campaigns file failed"/>
+  <int value="3" label="User pref is not available at campaigns matching"/>
+  <int value="4" label="Invalid campaign"/>
+  <int value="5" label="Invalid targeting"/>
+</enum>
+
 <enum name="CanaryCheckLookupResult">
   <int value="0" label="Success"/>
   <int value="1" label="Failure"/>
@@ -27311,7 +27324,7 @@
       label="OBSOLETE_V8RegExpUnicodeSetIncompatibilitiesWithUnicodeMode"/>
   <int value="4459" label="FedCmAutoReauthn"/>
   <int value="4460" label="TopicsAPIFetch"/>
-  <int value="4461" label="TopicsAPIXhr"/>
+  <int value="4461" label="OBSOLETE_TopicsAPIXhr"/>
   <int value="4462" label="ParseFromString"/>
   <int value="4463"
       label="OBSOLETE_HTMLPatternRegExpUnicodeSetIncompatibilitiesWithUnicodeMode"/>
@@ -57071,12 +57084,6 @@
   <int value="409" label="MSIXBUNDLE"/>
 </enum>
 
-<enum name="ScanCompleteAction">
-  <int value="0" label="Done button clicked"/>
-  <int value="1" label="Files app opened"/>
-  <int value="2" label="Media app opened"/>
-</enum>
-
 <enum name="ScanFailureProgress">
   <summary>
     Shows which function caused a failure while scanning. These functions
@@ -57102,51 +57109,6 @@
   <int value="9" label="Success"/>
 </enum>
 
-<enum name="ScanJobSettingsColorMode">
-  <int value="0" label="Black and white"/>
-  <int value="1" label="Grayscale"/>
-  <int value="2" label="Color"/>
-</enum>
-
-<enum name="ScanJobSettingsFileType">
-  <int value="0" label="JPG"/>
-  <int value="1" label="PDF"/>
-  <int value="2" label="PNG"/>
-</enum>
-
-<enum name="ScanJobSettingsPageSize">
-  <int value="0" label="A3"/>
-  <int value="1" label="A4"/>
-  <int value="2" label="B4"/>
-  <int value="3" label="Legal"/>
-  <int value="4" label="Letter"/>
-  <int value="5" label="Tabloid"/>
-  <int value="6" label="Max"/>
-</enum>
-
-<enum name="ScanJobSettingsResolution">
-  <int value="0" label="Unexpected dpi"/>
-  <int value="1" label="75 dpi"/>
-  <int value="2" label="100 dpi"/>
-  <int value="3" label="150 dpi"/>
-  <int value="4" label="200 dpi"/>
-  <int value="5" label="300 dpi"/>
-  <int value="6" label="600 dpi"/>
-</enum>
-
-<enum name="ScanJobSettingsSource">
-  <int value="0" label="Unknown"/>
-  <int value="1" label="Flatbed"/>
-  <int value="2" label="ADF Simplex"/>
-  <int value="3" label="ADF Duplex"/>
-  <int value="4" label="Default"/>
-</enum>
-
-<enum name="ScanMultiPageToolbarAction">
-  <int value="0" label="Remove page"/>
-  <int value="1" label="Rescan page"/>
-</enum>
-
 <enum name="ScopedTerminationCheckerStatus">
   <int value="0" label="Started"/>
   <int value="1" label="Finished"/>
@@ -57229,6 +57191,12 @@
   <int value="4" label="FRE Default Was Set"/>
   <int value="5" label="Learn More Screen Was Displayed"/>
   <int value="6" label="Learn More Screen Was Displayed in the FRE"/>
+  <int value="7"
+      label="Profile Creation Choice Screen Was Displayed (Desktop only)"/>
+  <int value="8" label="Profile Creation Default Was Set (Desktop only)"/>
+  <int value="9"
+      label="Learn More Screen Was Displayed in the Profile Creation Specific
+             Screen (Desktop only)"/>
 </enum>
 
 <enum name="SearchEntryPoint">
@@ -62359,14 +62327,6 @@
   <int value="7007" label="HIDDEN_to_HIDDEN"/>
 </enum>
 
-<enum name="VkPipelinePopulatedCacheEntryUsage">
-  <int value="0" label="Used"/>
-  <int value="1" label="Overwritten"/>
-  <int value="2" label="Discarded: too large"/>
-  <int value="3" label="Discarded: have newer data"/>
-  <int value="4" label="Evicted"/>
-</enum>
-
 <enum name="VkToUssMigrationStatus">
   <int value="0" label="Success"/>
   <int value="1" label="Failed persisting to USS"/>
diff --git a/tools/metrics/histograms/histograms_index.txt b/tools/metrics/histograms/histograms_index.txt
index 92e98feeb..6e07a6b 100644
--- a/tools/metrics/histograms/histograms_index.txt
+++ b/tools/metrics/histograms/histograms_index.txt
@@ -146,6 +146,7 @@
 tools/metrics/histograms/metadata/safe_browsing/histograms.xml
 tools/metrics/histograms/metadata/sb_client/enums.xml
 tools/metrics/histograms/metadata/sb_client/histograms.xml
+tools/metrics/histograms/metadata/scanning/enums.xml
 tools/metrics/histograms/metadata/scanning/histograms.xml
 tools/metrics/histograms/metadata/scheduler/histograms.xml
 tools/metrics/histograms/metadata/search/histograms.xml
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml
index 5c2df43..108d464 100644
--- a/tools/metrics/histograms/metadata/android/enums.xml
+++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -668,6 +668,15 @@
   <int value="96" label="GET_ATTRIBUTION_BEHAVIOR">
     WebSettingsCompat.getAttributionBehavior(int)
   </int>
+  <int value="97" label="GET_WEBVIEW_MEDIA_INTEGRITY_API_DEFAULT_STATUS">
+    WebSettingsCompat#getWebViewMediaIntegrityApiStatus()
+  </int>
+  <int value="98" label="GET_WEBVIEW_MEDIA_INTEGRITY_API_OVERRIDE_RULES">
+    WebSettingsCompat#getWebViewMediaIntegrityApiStatus()
+  </int>
+  <int value="99" label="SET_WEBVIEW_MEDIA_INTEGRITY_API_STATUS">
+    WebSettingsCompat#setWebViewMediaIntegrityApiStatus(int,Map(String,Integer))
+  </int>
 </enum>
 
 <enum name="AppPackageNameAllowlistParseStatus">
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index c8089d4..04aac2d 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -3641,6 +3641,16 @@
   </token>
 </histogram>
 
+<histogram name="Ash.Growth.CampaignsManager.Error"
+    enum="CampaignsManagerError" expires_after="2024-10-17">
+  <owner>llin@google.com</owner>
+  <owner>cros-growth@google.com</owner>
+  <summary>
+    Tracks unexpected error while loading and matching campaigns, recorded each
+    time an unexpected error occurs.
+  </summary>
+</histogram>
+
 <histogram name="Ash.Homescreen.AnimationSmoothness" units="%"
     expires_after="2024-04-28">
   <owner>sammiequon@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 80497da..dce01e1 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -3247,15 +3247,6 @@
   </summary>
 </histogram>
 
-<histogram name="Blink.Plugin.UpdateTime" units="ms" expires_after="2022-12-18">
-  <owner>cduvall@chromium.org</owner>
-  <owner>jam@chromium.org</owner>
-  <summary>
-    Measures the time it takes to update the plugin list. This can happen, for
-    example, during Navigator.plugins.get.
-  </summary>
-</histogram>
-
 <histogram name="Blink.PreloadRequestWaitTime" units="ms"
     expires_after="2023-10-22">
   <owner>cduvall@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
index 7bb81c15..03a1cd2 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
@@ -663,7 +663,7 @@
 </histogram>
 
 <histogram name="CustomTabs.TabCounts.OnClosingAllTabs" units="count"
-    expires_after="2023-10-08">
+    expires_after="2024-05-08">
   <owner>wenyufu@chromium.org</owner>
   <owner>chrome-connective-tissue@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 1e675530..b0550d3a 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1505,53 +1505,6 @@
   </summary>
 </histogram>
 
-<histogram name="GPU.Vulkan.PipelineCache.LoadCacheHit" enum="BooleanCacheHit"
-    expires_after="2022-05-08">
-  <owner>backer@chromium.org</owner>
-  <owner>penghuang@chromium.org</owner>
-  <owner>vasilyt@chromium.org</owner>
-  <summary>
-    Shows if we had a vkPipelineCache entry in cache when skia requested it.
-    Recorded each time skia loads vkPipelineCache entry from the GrShaderCache.
-  </summary>
-</histogram>
-
-<histogram name="GPU.Vulkan.PipelineCache.PopulatedCacheUsage"
-    enum="VkPipelinePopulatedCacheEntryUsage" expires_after="2021-10-31">
-  <owner>backer@chromium.org</owner>
-  <owner>penghuang@chromium.org</owner>
-  <owner>vasilyt@chromium.org</owner>
-  <summary>
-    Shows if the populated from disk cache entry was used by skia or discarded
-    and why. Recorded when cache entry that was read from disk is either loaded
-    by skia, overwriten by skia or discarded by GrShaderCache.
-  </summary>
-</histogram>
-
-<histogram name="GPU.Vulkan.PipelineCache.Size" units="KB"
-    expires_after="2022-05-08">
-  <owner>backer@chromium.org</owner>
-  <owner>penghuang@chromium.org</owner>
-  <owner>vasilyt@chromium.org</owner>
-  <summary>
-    Size of stored VkPipelineCache in kb. Recorded every time we store a
-    pipeline cache item. Currently happens when gpu goes idle after we stored or
-    loaded new shader.
-  </summary>
-</histogram>
-
-<histogram name="GPU.Vulkan.PipelineCache.StoreDuration" units="microseconds"
-    expires_after="2022-05-08">
-  <owner>backer@chromium.org</owner>
-  <owner>penghuang@chromium.org</owner>
-  <owner>vasilyt@chromium.org</owner>
-  <summary>
-    Duration of storeVkPipelineCacheData in Skia, this includes chromium side
-    GrShaderCache::store. Recorded even if data didn't fit into the cache. Only
-    reported for platforms supporting high resolution clocks.
-  </summary>
-</histogram>
-
 <histogram name="GPU.Vulkan.PipelineCache.vkCreateGraphicsPipelines"
     units="microseconds" expires_after="2023-12-10">
   <owner>penghuang@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index e58eff2b..429c37d 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -82,6 +82,16 @@
   </summary>
 </histogram>
 
+<histogram name="IOS.Annotations.UserTap.Cancelled" enum="Boolean"
+    expires_after="2024-08-08">
+  <owner>djean@google.com</owner>
+  <owner>bling-team@chromium.org</owner>
+  <summary>
+    Whether or not a user tap on an annotation got cancelled because some other
+    script in the web page triggered some mutations on the same tap.
+  </summary>
+</histogram>
+
 <histogram name="IOS.AppLauncher.AppURLHasChromeLaunchScheme" enum="Boolean"
     expires_after="2024-05-30">
   <owner>ajuma@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/net/enums.xml b/tools/metrics/histograms/metadata/net/enums.xml
index a7221822..49f34282 100644
--- a/tools/metrics/histograms/metadata/net/enums.xml
+++ b/tools/metrics/histograms/metadata/net/enums.xml
@@ -444,6 +444,26 @@
   <int value="18" label="QuicProtocolError"/>
 </enum>
 
+<enum name="HttpRequestStsState">
+  <summary>
+    Tracks whether main frame requests are protected by HSTS, which maps closely
+    to whether they're vulnerable to a downgrade attack.
+  </summary>
+  <int value="0" label="kUnknown">Unknown state</int>
+  <int value="1" label="kUnprotectedHttps">
+    The request used HTTPS, but was not protected by HSTS.
+  </int>
+  <int value="2" label="kProtectedHttps">
+    The request used HTTPS and was protected by HSTS.
+  </int>
+  <int value="3" label="kUnprotectedHttp">
+    The request used HTTP and was unprotected by HSTS.
+  </int>
+  <int value="4" label="kProtectedHttp">
+    The request used HTTP and was redirected due to HSTS.
+  </int>
+</enum>
+
 <enum name="HttpssvcDnsRcode">
   <summary>
     Tracks the result of querying for an INTEGRITY or HTTPSSVC record. This enum
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 44bbcc1b..52c8bbb 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -2098,6 +2098,17 @@
   </summary>
 </histogram>
 
+<histogram name="Net.HttpRequestStsState" enum="HttpRequestStsState"
+    expires_after="2024-11-09">
+  <owner>jdeblasio@chromium.org</owner>
+  <owner>trusty-transport@chromium.org</owner>
+  <summary>
+    The HSTS status of top-frame HTTP/WS requests made by UrlRequestHttpJob.
+    Requests with kProtectedHttp are immediately internally redirected to a
+    secure request.
+  </summary>
+</histogram>
+
 <histogram name="Net.HttpResponseCode" enum="HttpResponseCode"
     expires_after="never">
 <!-- expires-never: Core network stack health metric -->
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 483d32c6..f56f640 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -625,6 +625,20 @@
   </token>
 </histogram>
 
+<histogram name="Ads.InterestGroup.Auction.LoadGroupsCacheHit" enum="Boolean"
+    expires_after="2024-06-20">
+  <owner>abigailkatcoff@chromium.org</owner>
+  <owner>privacy-sandbox-dev@chromium.org</owner>
+  <summary>
+    True if there is a cache hit when loading an owner's interest groups for a
+    FLEDGE auction. Recorded whenever an owner's interest groups are loaded for
+    an auction and the cache is enabled via FledgeUseInterestGroupCache.
+
+    See https://github.com/WICG/turtledove/blob/main/FLEDGE.md for the latest
+    version of the FLEDGE explainer.
+  </summary>
+</histogram>
+
 <histogram name="Ads.InterestGroup.Auction.LoadGroupsTime" units="ms"
     expires_after="2024-06-20">
   <owner>pauljensen@chromium.org</owner>
@@ -3544,17 +3558,6 @@
   </summary>
 </histogram>
 
-<histogram name="DefaultBrowser.Win10ChooserInvoked" enum="BooleanSuccess"
-    expires_after="M85">
-  <owner>pmonette@chromium.org</owner>
-  <summary>
-    When changing the default browser on Windows 10, records whether the browser
-    chooser is successfully invoked when opening the settings page. This
-    histogram is only recorded when the Win10AcceleratedDefaultBrowserFlow
-    experiment is enabled.
-  </summary>
-</histogram>
-
 <histogram name="DefaultProtocolClient.SetDefaultResult2"
     enum="DefaultWebClientState" expires_after="M77">
   <owner>pmonette@chromium.org</owner>
@@ -4886,7 +4889,7 @@
 </histogram>
 
 <histogram name="Feedback.TrustSafetySentiment.CallTriggerOccurred"
-    enum="TrustSafetySentimentFeatureArea" expires_after="2023-11-14">
+    enum="TrustSafetySentimentFeatureArea" expires_after="2024-11-14">
   <owner>skrakowi@google.com</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/page/histograms.xml b/tools/metrics/histograms/metadata/page/histograms.xml
index a8c2f78..043dd56 100644
--- a/tools/metrics/histograms/metadata/page/histograms.xml
+++ b/tools/metrics/histograms/metadata/page/histograms.xml
@@ -276,6 +276,7 @@
     expires_after="2024-04-17">
   <owner>alexmt@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
+  <owner>chrome-ads-histograms@google.com</owner>
   <summary>
     Total number of network bytes that went towards loading non-ad resources for
     a single page over its entire lifetime. This includes resources that did not
@@ -302,6 +303,7 @@
     expires_after="2024-04-17">
   <owner>alexmt@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
+  <owner>chrome-ads-histograms@google.com</owner>
   <summary>
     The percentage of all bytes loaded by the page that are attributed to ad
     resources (including ad resources in the main frame) across the entire page
@@ -316,6 +318,7 @@
   <owner>yaoxia@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
+  <owner>chrome-ads-histograms@google.com</owner>
   <summary>
     The estimated average viewport ad density. Each density sample value is
     calculated as the area of (iframe and img) ads within the viewport * 100 /
@@ -329,6 +332,7 @@
     enum="BooleanBlocked" expires_after="2024-04-17">
   <owner>johnidel@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
+  <owner>chrome-ads-histograms@google.com</owner>
   <summary>
     Records whether the heavy ad intervention was disallowed by the blocklist on
     a frame. Only recorded when the heavy ad blocklist is enabled and available
@@ -339,7 +343,7 @@
 <histogram name="PageLoad.Clients.Ads.HeavyAds.NetworkBytesAtFrameUnload"
     units="bytes" expires_after="2024-04-17">
   <owner>johnidel@chromium.org</owner>
-  <owner>justinmiron@google.com</owner>
+  <owner>chrome-ads-histograms@google.com</owner>
   <summary>
     Records the number of network bytes used by a heavy ad unloaded due to
     network usage by the heavy ad intervention. It is recorded at the time of
@@ -349,7 +353,7 @@
 </histogram>
 
 <histogram name="PageLoad.Clients.Ads.Memory.MainFrame.Max" units="KiB"
-    expires_after="2022-03-06">
+    expires_after="2024-11-10">
   <owner>cammie@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
@@ -362,11 +366,14 @@
     PageLoadMetrics when the page is destroyed. An ad frame consists of the
     identified ad frame and all of its children (which may also be ads, but are
     counted as part of the ancestor ad frame).
+
+    Warning: This histogram has incomplete data. It expired in 03/2022 and was
+    unexpired in 11/2023.
   </summary>
 </histogram>
 
 <histogram name="PageLoad.Clients.Ads.Memory.UpdateCount" units="count"
-    expires_after="2022-06-12">
+    expires_after="2024-11-10">
   <owner>cammie@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
@@ -376,6 +383,9 @@
     AdsPageLoadMetricsObserver per pageload. Only recorded if the page has at
     least one identified ad frame. Recorded in PageLoadMetrics when the page is
     destroyed.
+
+    Warning: This histogram has incomplete data. It expired in 06/2022 and was
+    unexpired in 11/2023.
   </summary>
 </histogram>
 
@@ -383,6 +393,7 @@
     expires_after="2024-04-17">
   <owner>johnidel@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
+  <owner>chrome-ads-histograms@google.com</owner>
   <summary>
     Total number of network bytes that went towards loading ad resources for a
     single page over it's entire lifetime. This includes resources that did not
@@ -1621,24 +1632,29 @@
 </histogram>
 
 <histogram name="PageLoad.Experimental.Memory.Core.MainFrame.Max" units="KiB"
-    expires_after="2022-03-06">
+    expires_after="2024-11-10">
   <owner>cammie@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
+  <owner>chrome-ads-histograms@google.com</owner>
   <summary>
     The maximum reported number of kilobytes of memory used by V8 by the main
     frame in this pageload.
 
     Only recorded if memory tracking is enabled. Recorded in PageLoadMetrics
     when the page is destroyed.
+
+    Warning: This histogram has incomplete data. It expired in 03/2022 and was
+    unexpired in 11/2023.
   </summary>
 </histogram>
 
 <histogram name="PageLoad.Experimental.Memory.Core.Subframe.Aggregate.Max"
-    units="KiB" expires_after="2022-03-06">
+    units="KiB" expires_after="2024-11-10">
   <owner>cammie@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
+  <owner>chrome-ads-histograms@google.com</owner>
   <summary>
     The maximum reported aggregate number of kilobytes of memory used by V8 by
     subframes in this pageload. The value recorded is the maximum simultaneous
@@ -1646,14 +1662,18 @@
 
     Only recorded if memory tracking is enabled. Recorded in PageLoadMetrics
     when the page is destroyed.
+
+    Warning: This histogram has incomplete data. It expired in 03/2022 and was
+    unexpired in 11/2023.
   </summary>
 </histogram>
 
 <histogram name="PageLoad.Experimental.Memory.Core.Total.Max" units="KiB"
-    expires_after="2022-03-06">
+    expires_after="2024-11-10">
   <owner>cammie@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
+  <owner>chrome-ads-histograms@google.com</owner>
   <summary>
     The maximum reported aggregate number of kilobytes of memory used by V8 by
     all frames in this pageload. The value recorded is the maximum simultaneous
@@ -1661,6 +1681,9 @@
 
     Only recorded if memory tracking is enabled. Recorded in PageLoadMetrics
     when the page is destroyed.
+
+    Warning: This histogram has incomplete data. It expired in 03/2022 and was
+    unexpired in 11/2023.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/privacy/histograms.xml b/tools/metrics/histograms/metadata/privacy/histograms.xml
index 6fab9bd..0481014 100644
--- a/tools/metrics/histograms/metadata/privacy/histograms.xml
+++ b/tools/metrics/histograms/metadata/privacy/histograms.xml
@@ -620,6 +620,18 @@
   </summary>
 </histogram>
 
+<histogram name="PrivacySandbox.Attestations.IsSiteAttested"
+    enum="PrivacySandboxApiAllowed" expires_after="2024-04-28">
+  <owner>shivanisha@chromium.org</owner>
+  <owner>xiaochenzh@chromium.org</owner>
+  <summary>
+    Records the status of Privacy Sandbox APIs attestation checks. Each time a
+    Privacy Sandbox API attestation check takes place, the status is recorded.
+    The status can be allowed, or denied with reasons. This is an aggregate
+    metric across all Privacy Sandbox APIs that require attestations.
+  </summary>
+</histogram>
+
 <histogram
     name="PrivacySandbox.CookieDeprecationFacilitatedTesting.ReasonForComputedEligibilityForProfile"
     enum="CookieDeprecationFacilitatedTestingProfileEligibility"
diff --git a/tools/metrics/histograms/metadata/scanning/enums.xml b/tools/metrics/histograms/metadata/scanning/enums.xml
new file mode 100644
index 0000000..7f3b03c
--- /dev/null
+++ b/tools/metrics/histograms/metadata/scanning/enums.xml
@@ -0,0 +1,82 @@
+<!--
+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.
+-->
+
+<!--
+
+This file describes the enumerations referenced by entries in histograms.xml for
+this directory. Some enums may instead be listed in the central enums.xml file
+at src/tools/metrics/histograms/enums.xml when multiple files use them.
+
+For best practices on writing enumerations descriptions, see
+https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/histograms/README.md#Enum-Histograms
+
+Please follow the instructions in the OWNERS file in this directory to find a
+reviewer. If no OWNERS file exists, please consider signing up at
+go/reviewing-metrics (Googlers only), as all subdirectories are expected to
+have an OWNERS file. As a last resort you can send the CL to
+chromium-metrics-reviews@google.com.
+-->
+
+<histogram-configuration>
+
+<!-- Enum types -->
+
+<enums>
+
+<enum name="ScanCompleteAction">
+  <int value="0" label="Done button clicked"/>
+  <int value="1" label="Files app opened"/>
+  <int value="2" label="Media app opened"/>
+</enum>
+
+<enum name="ScanJobSettingsColorMode">
+  <int value="0" label="Black and white"/>
+  <int value="1" label="Grayscale"/>
+  <int value="2" label="Color"/>
+</enum>
+
+<enum name="ScanJobSettingsFileType">
+  <int value="0" label="JPG"/>
+  <int value="1" label="PDF"/>
+  <int value="2" label="PNG"/>
+</enum>
+
+<enum name="ScanJobSettingsPageSize">
+  <int value="0" label="A3"/>
+  <int value="1" label="A4"/>
+  <int value="2" label="B4"/>
+  <int value="3" label="Legal"/>
+  <int value="4" label="Letter"/>
+  <int value="5" label="Tabloid"/>
+  <int value="6" label="Max"/>
+</enum>
+
+<enum name="ScanJobSettingsResolution">
+  <int value="0" label="Unexpected dpi"/>
+  <int value="1" label="75 dpi"/>
+  <int value="2" label="100 dpi"/>
+  <int value="3" label="150 dpi"/>
+  <int value="4" label="200 dpi"/>
+  <int value="5" label="300 dpi"/>
+  <int value="6" label="600 dpi"/>
+</enum>
+
+<enum name="ScanJobSettingsSource">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Flatbed"/>
+  <int value="2" label="ADF Simplex"/>
+  <int value="3" label="ADF Duplex"/>
+  <int value="4" label="Default"/>
+</enum>
+
+<enum name="ScanMultiPageToolbarAction">
+  <int value="0" label="Remove page"/>
+  <int value="1" label="Rescan page"/>
+</enum>
+
+</enums>
+
+</histogram-configuration>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 78c1845..af37e6a 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,28 +1,28 @@
 {
     "trace_processor_shell": {
         "linux_arm64": {
-            "hash": "f1ebce4fc0b92ffaa07ad48ac6ed9113f0528962",
-            "full_remote_path": "perfetto-luci-artifacts/3f522a981c45dc5d735a5e5d775c70188adff93c/linux-arm64/trace_processor_shell"
+            "hash": "0a40a431efa1cea2835678291c48a7b0ba5dc54c",
+            "full_remote_path": "perfetto-luci-artifacts/e9ec2882302006a4244527c472a3e4b217ba4337/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "5244c4a1f5fa197b4379477b3305e31f836c351c",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/11e588ed3a8fc568f13a4ae156fa848d00909937/trace_processor_shell.exe"
+            "hash": "83e3c874c7a0105c8e31fb5522af917abfd081bb",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/3167ef8f02a3756c5abb75b041ba33c902bfe1d1/trace_processor_shell.exe"
         },
         "linux_arm": {
-            "hash": "a6d798c6ea35705f2ab9ace2b224c32e376eb0ce",
-            "full_remote_path": "perfetto-luci-artifacts/3f522a981c45dc5d735a5e5d775c70188adff93c/linux-arm/trace_processor_shell"
+            "hash": "2554321cf2678e66a6013b2719a05403791a5bc8",
+            "full_remote_path": "perfetto-luci-artifacts/e9ec2882302006a4244527c472a3e4b217ba4337/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "b72021acf93ec3bdcca6fc3da2fd6e290ee11263",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/11e588ed3a8fc568f13a4ae156fa848d00909937/trace_processor_shell"
+            "hash": "d2f9b340d27e126852b82843c7bc2974ce2ddfcf",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/3167ef8f02a3756c5abb75b041ba33c902bfe1d1/trace_processor_shell"
         },
         "mac_arm64": {
-            "hash": "cc287491e9ff9fe2c4866e5574eaea04134895a0",
-            "full_remote_path": "perfetto-luci-artifacts/v38.0/mac-arm64/trace_processor_shell"
+            "hash": "092ddd410a3fa7415e8ca703b5b0472eb0da7678",
+            "full_remote_path": "perfetto-luci-artifacts/e9ec2882302006a4244527c472a3e4b217ba4337/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "a0a5e35797a177ab001400797a487094caacf872",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/11e588ed3a8fc568f13a4ae156fa848d00909937/trace_processor_shell"
+            "hash": "db5cd5de0ce5c2520486fd62e5debb4c627bd67c",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/3167ef8f02a3756c5abb75b041ba33c902bfe1d1/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc
index 6565775..1bdce794 100644
--- a/ui/accessibility/accessibility_features.cc
+++ b/ui/accessibility/accessibility_features.cc
@@ -14,100 +14,114 @@
 
 namespace features {
 
-// Enable recognizing "aria-virtualcontent" as a valid aria property.
-BASE_FEATURE(kEnableAccessibilityAriaVirtualContent,
-             "AccessibilityAriaVirtualContent",
+BASE_FEATURE(kAblateSendPendingAccessibilityEvents,
+             "AblateSendPendingAccessibilityEvents",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAccessibilityAriaVirtualContentEnabled() {
+bool IsAblateSendPendingAccessibilityEventsEnabled() {
   return base::FeatureList::IsEnabled(
-      ::features::kEnableAccessibilityAriaVirtualContent);
-}
-
-// Enable exposing the <html> element to the browser process AXTree
-// (as an ignored node).
-BASE_FEATURE(kEnableAccessibilityExposeHTMLElement,
-             "AccessibilityExposeHTMLElement",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-bool IsAccessibilityExposeHTMLElementEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kEnableAccessibilityExposeHTMLElement);
-}
-
-// Enable exposing ignored nodes from Blink to the browser process AXTree.
-// This will allow us to simplify logic by eliminating the distiction between
-// "ignored and included in the tree" from "ignored and not included in the
-// tree".
-BASE_FEATURE(kEnableAccessibilityExposeIgnoredNodes,
-             "AccessibilityExposeIgnoredNodes",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAccessibilityExposeIgnoredNodesEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kEnableAccessibilityExposeIgnoredNodes);
-}
-
-// Enable language detection to determine language used in page text, exposed
-// on the browser process AXTree.
-BASE_FEATURE(kEnableAccessibilityLanguageDetection,
-             "AccessibilityLanguageDetection",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAccessibilityLanguageDetectionEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kEnableAccessibilityLanguageDetection);
-}
-
-// Serializes accessibility information from the Views tree and deserializes it
-// into an AXTree in the browser process.
-BASE_FEATURE(kEnableAccessibilityTreeForViews,
-             "AccessibilityTreeForViews",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAccessibilityTreeForViewsEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kEnableAccessibilityTreeForViews);
-}
-
-BASE_FEATURE(kEnableAccessibilityRestrictiveIA2AXModes,
-             "AccessibilityRestrictiveIA2AXModes",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-bool IsAccessibilityRestrictiveIA2AXModesEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kEnableAccessibilityRestrictiveIA2AXModes);
+      ::features::kAblateSendPendingAccessibilityEvents);
 }
 
 BASE_FEATURE(kAccessibilityFocusHighlight,
              "AccessibilityFocusHighlight",
              base::FEATURE_ENABLED_BY_DEFAULT);
-
 bool IsAccessibilityFocusHighlightEnabled() {
   return base::FeatureList::IsEnabled(::features::kAccessibilityFocusHighlight);
 }
 
+BASE_FEATURE(kAugmentExistingImageLabels,
+             "AugmentExistingImageLabels",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsAugmentExistingImageLabelsEnabled() {
+  return base::FeatureList::IsEnabled(::features::kAugmentExistingImageLabels);
+}
+
 BASE_FEATURE(kAutoDisableAccessibility,
              "AutoDisableAccessibility",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
 bool IsAutoDisableAccessibilityEnabled() {
   return base::FeatureList::IsEnabled(::features::kAutoDisableAccessibility);
 }
 
+BASE_FEATURE(kBacklightOcr, "BacklightOcr", base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsBacklightOcrEnabled() {
+  return base::FeatureList::IsEnabled(features::kBacklightOcr);
+}
+
+BASE_FEATURE(kEnableAccessibilityAriaVirtualContent,
+             "AccessibilityAriaVirtualContent",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsAccessibilityAriaVirtualContentEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kEnableAccessibilityAriaVirtualContent);
+}
+
+BASE_FEATURE(kEnableAccessibilityExposeHTMLElement,
+             "AccessibilityExposeHTMLElement",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+bool IsAccessibilityExposeHTMLElementEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kEnableAccessibilityExposeHTMLElement);
+}
+
+BASE_FEATURE(kEnableAccessibilityExposeIgnoredNodes,
+             "AccessibilityExposeIgnoredNodes",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsAccessibilityExposeIgnoredNodesEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kEnableAccessibilityExposeIgnoredNodes);
+}
+
+BASE_FEATURE(kEnableAccessibilityLanguageDetection,
+             "AccessibilityLanguageDetection",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsAccessibilityLanguageDetectionEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kEnableAccessibilityLanguageDetection);
+}
+
+BASE_FEATURE(kEnableAccessibilityRestrictiveIA2AXModes,
+             "AccessibilityRestrictiveIA2AXModes",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+bool IsAccessibilityRestrictiveIA2AXModesEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kEnableAccessibilityRestrictiveIA2AXModes);
+}
+
+BASE_FEATURE(kEnableAccessibilityTreeForViews,
+             "AccessibilityTreeForViews",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsAccessibilityTreeForViewsEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kEnableAccessibilityTreeForViews);
+}
+
+BASE_FEATURE(kEnableAriaElementReflection,
+             "EnableAriaElementReflection",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsAriaElementReflectionEnabled() {
+  return base::FeatureList::IsEnabled(::features::kEnableAriaElementReflection);
+}
+
 BASE_FEATURE(kTextBasedAudioDescription,
              "TextBasedAudioDescription",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
 bool IsTextBasedAudioDescriptionEnabled() {
   return base::FeatureList::IsEnabled(::features::kTextBasedAudioDescription);
 }
 
+BASE_FEATURE(kUseAXPositionForDocumentMarkers,
+             "UseAXPositionForDocumentMarkers",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsUseAXPositionForDocumentMarkersEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kUseAXPositionForDocumentMarkers);
+}
+
 #if BUILDFLAG(IS_WIN)
 BASE_FEATURE(kIChromeAccessible,
              "IChromeAccessible",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
 bool IsIChromeAccessibleEnabled() {
   return base::FeatureList::IsEnabled(::features::kIChromeAccessible);
 }
@@ -115,16 +129,11 @@
 BASE_FEATURE(kSelectiveUIAEnablement,
              "SelectiveUIAEnablement",
              base::FEATURE_ENABLED_BY_DEFAULT);
-
-// Returns true if accessibility will be selectively enabled depending on the
-// UIA APIs that are called, allowing non-screenreader usage to enable less of
-// the accessibility system.
 bool IsSelectiveUIAEnablementEnabled() {
   return base::FeatureList::IsEnabled(::features::kSelectiveUIAEnablement);
 }
 
 BASE_FEATURE(kUiaProvider, "UiaProvider", base::FEATURE_DISABLED_BY_DEFAULT);
-
 bool IsUiaProviderEnabled() {
   return base::FeatureList::IsEnabled(kUiaProvider);
 }
@@ -136,10 +145,48 @@
       ash::features::kOnDeviceSpeechRecognition);
 }
 
+BASE_FEATURE(kAccessibilityAcceleratorNotificationsTimeout,
+             "AccessibilityAcceleratorNotificationsTimeout",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+bool IsAccessibilityAcceleratorNotificationsTimeoutEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kAccessibilityAcceleratorNotificationsTimeout);
+}
+
+BASE_FEATURE(kAccessibilityDictationKeyboardImprovements,
+             "AccessibilityDictationKeyboardImprovements",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+bool IsAccessibilityDictationKeyboardImprovementsEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kAccessibilityDictationKeyboardImprovements);
+}
+
+BASE_FEATURE(kAccessibilityFaceGaze,
+             "AccessibilityFaceGaze",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsAccessibilityFaceGazeEnabled() {
+  return base::FeatureList::IsEnabled(::features::kAccessibilityFaceGaze);
+}
+
+BASE_FEATURE(kAccessibilitySelectToSpeakHoverTextImprovements,
+             "AccessibilitySelectToSpeakHoverTextImprovements",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+bool IsAccessibilitySelectToSpeakHoverTextImprovementsEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kAccessibilitySelectToSpeakHoverTextImprovements);
+}
+
+BASE_FEATURE(kExperimentalAccessibilityGoogleTtsHighQualityVoices,
+             "ExperimentalAccessibilityGoogleTtsHighQualityVoices",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsExperimentalAccessibilityGoogleTtsHighQualityVoicesEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kExperimentalAccessibilityGoogleTtsHighQualityVoices);
+}
+
 BASE_FEATURE(kExperimentalAccessibilityDictationContextChecking,
              "ExperimentalAccessibilityDictationContextChecking",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
 bool IsExperimentalAccessibilityDictationContextCheckingEnabled() {
   return base::FeatureList::IsEnabled(
       ::features::kExperimentalAccessibilityDictationContextChecking);
@@ -148,101 +195,16 @@
 BASE_FEATURE(kExperimentalAccessibilityGoogleTtsLanguagePacks,
              "ExperimentalAccessibilityGoogleTtsLanguagePacks",
              base::FEATURE_ENABLED_BY_DEFAULT);
-
 bool IsExperimentalAccessibilityGoogleTtsLanguagePacksEnabled() {
   return base::FeatureList::IsEnabled(
       ::features::kExperimentalAccessibilityGoogleTtsLanguagePacks);
 }
-
-BASE_FEATURE(kExperimentalAccessibilityGoogleTtsHighQualityVoices,
-             "ExperimentalAccessibilityGoogleTtsHighQualityVoices",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsExperimentalAccessibilityGoogleTtsHighQualityVoicesEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kExperimentalAccessibilityGoogleTtsHighQualityVoices);
-}
-
-BASE_FEATURE(kAccessibilityDictationKeyboardImprovements,
-             "AccessibilityDictationKeyboardImprovements",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-bool IsAccessibilityDictationKeyboardImprovementsEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kAccessibilityDictationKeyboardImprovements);
-}
-
-BASE_FEATURE(kAccessibilitySelectToSpeakHoverTextImprovements,
-             "AccessibilitySelectToSpeakHoverTextImprovements",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-bool IsAccessibilitySelectToSpeakHoverTextImprovementsEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kAccessibilitySelectToSpeakHoverTextImprovements);
-}
-
-BASE_FEATURE(kAccessibilityAcceleratorNotificationsTimeout,
-             "AccessibilityAcceleratorNotificationsTimeout",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-bool IsAccessibilityAcceleratorNotificationsTimeoutEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kAccessibilityAcceleratorNotificationsTimeout);
-}
-
-BASE_FEATURE(kAccessibilityFaceGaze,
-             "AccessibilityFaceGaze",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAccessibilityFaceGazeEnabled() {
-  return base::FeatureList::IsEnabled(::features::kAccessibilityFaceGaze);
-}
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-BASE_FEATURE(kBacklightOcr, "BacklightOcr", base::FEATURE_DISABLED_BY_DEFAULT);
-bool IsBacklightOcrEnabled() {
-  return base::FeatureList::IsEnabled(features::kBacklightOcr);
-}
-
-BASE_FEATURE(kAugmentExistingImageLabels,
-             "AugmentExistingImageLabels",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAugmentExistingImageLabelsEnabled() {
-  return base::FeatureList::IsEnabled(::features::kAugmentExistingImageLabels);
-}
-
-BASE_FEATURE(kUseAXPositionForDocumentMarkers,
-             "UseAXPositionForDocumentMarkers",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsUseAXPositionForDocumentMarkersEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kUseAXPositionForDocumentMarkers);
-}
-
-BASE_FEATURE(kEnableAriaElementReflection,
-             "EnableAriaElementReflection",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAriaElementReflectionEnabled() {
-  return base::FeatureList::IsEnabled(::features::kEnableAriaElementReflection);
-}
-
-BASE_FEATURE(kAblateSendPendingAccessibilityEvents,
-             "AblateSendPendingAccessibilityEvents",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAblateSendPendingAccessibilityEventsEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kAblateSendPendingAccessibilityEvents);
-}
-
 #if BUILDFLAG(IS_ANDROID)
 BASE_FEATURE(kAccessibilityPerformanceFiltering,
              "AccessibilityPerformanceFiltering",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
 bool IsAccessibilityPerformanceFilteringEnabled() {
   return base::FeatureList::IsEnabled(
       ::features::kAccessibilityPerformanceFiltering);
@@ -251,7 +213,6 @@
 BASE_FEATURE(kAccessibilitySnapshotStressTests,
              "AccessibilitySnapshotStressTests",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
 bool IsAccessibilitySnapshotStressTestsEnabled() {
   return base::FeatureList::IsEnabled(
       ::features::kAccessibilitySnapshotStressTests);
@@ -259,16 +220,6 @@
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if !BUILDFLAG(IS_ANDROID)
-BASE_FEATURE(kReadAnything, "ReadAnything", base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsReadAnythingEnabled() {
-  return base::FeatureList::IsEnabled(::features::kReadAnything);
-}
-
-BASE_FEATURE(kReadAnythingWithScreen2x,
-             "ReadAnythingWithScreen2x",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // This feature can be used as an emergency kill switch to disable Screen AI
 // main content extraction service in case of security or other issues.
 // Please talk to components/services/screen_ai/OWNERS if any changes to this
@@ -277,33 +228,6 @@
              "EmergencyDisableScreenAIMainContentExtraction",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-bool IsReadAnythingWithScreen2xEnabled() {
-  return base::FeatureList::IsEnabled(::features::kReadAnythingWithScreen2x) &&
-         !base::FeatureList::IsEnabled(
-             ::features::kEmergencyDisableScreenAIMainContentExtraction);
-}
-
-// This feature is only used for generating training data for Screen2x and should
-// never be used in any other circumstance, and should not be enabled by default.
-BASE_FEATURE(kDataCollectionModeForScreen2x,
-             "DataCollectionModeForScreen2x",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsDataCollectionModeForScreen2xEnabled() {
-  return base::FeatureList::IsEnabled(
-      ::features::kDataCollectionModeForScreen2x);
-}
-
-// This feature is only for debug purposes and for security/privacy reasons,
-// should be never enabled by default .
-BASE_FEATURE(kScreenAIDebugMode,
-             "ScreenAIDebugMode",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsScreenAIDebugModeEnabled() {
-  return base::FeatureList::IsEnabled(::features::kScreenAIDebugMode);
-}
-
 // This feature can be used as an emergency kill switch to disable Screen AI
 // OCR service in case of security or other issues.
 // Please talk to components/services/screen_ai/OWNERS if any changes to this
@@ -312,28 +236,29 @@
              "EmergencyDisableScreenAIOCR",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kReadAnythingWebUIToolbar,
-             "ReadAnythingWebUIToolbar",
+BASE_FEATURE(kAccessibilityService,
+             "AccessibilityService",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsReadAnythingWebUIToolbarEnabled() {
-  return base::FeatureList::IsEnabled(::features::kReadAnythingWebUIToolbar);
+bool IsAccessibilityServiceEnabled() {
+  return base::FeatureList::IsEnabled(::features::kAccessibilityService);
 }
 
-BASE_FEATURE(kReadAnythingReadAloud,
-             "ReadAnythingReadAloud",
+// This feature is only used for generating training data for Screen2x and
+// should never be used in any other circumstance, and should not be enabled by
+// default.
+BASE_FEATURE(kDataCollectionModeForScreen2x,
+             "DataCollectionModeForScreen2x",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsReadAnythingReadAloudEnabled() {
-  return base::FeatureList::IsEnabled(::features::kReadAnythingReadAloud);
+bool IsDataCollectionModeForScreen2xEnabled() {
+  return base::FeatureList::IsEnabled(
+      ::features::kDataCollectionModeForScreen2x);
 }
 
-BASE_FEATURE(kReadAnythingOmniboxIcon,
-             "ReadAnythingOmniboxIcon",
+BASE_FEATURE(kLayoutExtraction,
+             "LayoutExtraction",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsReadAnythingOmniboxIconEnabled() {
-  return base::FeatureList::IsEnabled(::features::kReadAnythingOmniboxIcon);
+bool IsLayoutExtractionEnabled() {
+  return base::FeatureList::IsEnabled(::features::kLayoutExtraction);
 }
 
 BASE_FEATURE(kPdfOcr,
@@ -351,34 +276,58 @@
              ::features::kEmergencyDisableScreenAIOCR);
 }
 
-BASE_FEATURE(kLayoutExtraction,
-             "LayoutExtraction",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsLayoutExtractionEnabled() {
-  return base::FeatureList::IsEnabled(::features::kLayoutExtraction);
+BASE_FEATURE(kReadAnything, "ReadAnything", base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsReadAnythingEnabled() {
+  return base::FeatureList::IsEnabled(::features::kReadAnything);
 }
 
-BASE_FEATURE(kAccessibilityService,
-             "AccessibilityService",
+BASE_FEATURE(kReadAnythingOmniboxIcon,
+             "ReadAnythingOmniboxIcon",
              base::FEATURE_DISABLED_BY_DEFAULT);
-
-bool IsAccessibilityServiceEnabled() {
-  return base::FeatureList::IsEnabled(::features::kAccessibilityService);
+bool IsReadAnythingOmniboxIconEnabled() {
+  return base::FeatureList::IsEnabled(::features::kReadAnythingOmniboxIcon);
 }
 
+BASE_FEATURE(kReadAnythingReadAloud,
+             "ReadAnythingReadAloud",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsReadAnythingReadAloudEnabled() {
+  return base::FeatureList::IsEnabled(::features::kReadAnythingReadAloud);
+}
+
+BASE_FEATURE(kReadAnythingWebUIToolbar,
+             "ReadAnythingWebUIToolbar",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsReadAnythingWebUIToolbarEnabled() {
+  return base::FeatureList::IsEnabled(::features::kReadAnythingWebUIToolbar);
+}
+
+BASE_FEATURE(kReadAnythingWithScreen2x,
+             "ReadAnythingWithScreen2x",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsReadAnythingWithScreen2xEnabled() {
+  return base::FeatureList::IsEnabled(::features::kReadAnythingWithScreen2x) &&
+         !base::FeatureList::IsEnabled(
+             ::features::kEmergencyDisableScreenAIMainContentExtraction);
+}
+
+// This feature is only for debug purposes and for security/privacy reasons,
+// should be never enabled by default .
+BASE_FEATURE(kScreenAIDebugMode,
+             "ScreenAIDebugMode",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+bool IsScreenAIDebugModeEnabled() {
+  return base::FeatureList::IsEnabled(::features::kScreenAIDebugMode);
+}
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_MAC)
-
 BASE_FEATURE(kAccessibilityRemoteUIApp,
              "AccessibilityRemoteUIApp",
              base::FEATURE_ENABLED_BY_DEFAULT);
-
 bool IsAccessibilityRemoteUIAppEnabled() {
   return base::FeatureList::IsEnabled(::features::kAccessibilityRemoteUIApp);
 }
-
 #endif  // BUILDFLAG(IS_MAC)
 
 }  // namespace features
diff --git a/ui/accessibility/accessibility_features.h b/ui/accessibility/accessibility_features.h
index 5fc6c89..8542a3b 100644
--- a/ui/accessibility/accessibility_features.h
+++ b/ui/accessibility/accessibility_features.h
@@ -11,283 +11,240 @@
 #include "build/chromeos_buildflags.h"
 #include "ui/accessibility/ax_base_export.h"
 
+// This file declares base::Features related to the ui/accessibility code.
+//
+// If your flag is for all platforms, include it in the first section. If
+// the flag is build-specific, include it in the appropriate section or
+// add a new section if needed.
+//
+// Keep all sections ordered alphabetically.
+//
+// Include the base declaration, and a bool "is...Enabled()" getter for
+// convenience and consistency. The bool method does not need a comment. Place a
+// comment above the feature flag describing what the flag does when enabled.
+// For example, a new entry should look like:
+//
+//    // <<effect of the experiment>>
+//    AX_BASE_EXPORT BASE_DECLARE_FEATURE(kNewFeature);
+//    AX_BASE_EXPORT bool IsNewFeatureEnabled();
+//
+// In the .cc file, a corresponding new entry should look like:
+//
+//    BASE_FEATURE(kNewFeature, "NewFeature",
+//    base::FEATURE_DISABLED_BY_DEFAULT); bool IsNewFeatureEnabled() {
+//      return base::FeatureList::IsEnabled(::features::kNewFeature);
+//    }
+//
+// Your feature name should start with "kAccessibility". There is no need to
+// include the words "enabled" or "experimental", as these are implied. We
+// include accessibility to differentiate these features from others in
+// Chromium.
+
 namespace features {
 
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityAriaVirtualContent);
+// Increase the cost of SendPendingAccessibilityEvents.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAblateSendPendingAccessibilityEvents);
+AX_BASE_EXPORT bool IsAblateSendPendingAccessibilityEventsEnabled();
 
-// Returns true if "aria-virtualcontent" should be recognized as a valid aria
-// property.
-AX_BASE_EXPORT bool IsAccessibilityAriaVirtualContentEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityExposeHTMLElement);
-
-// Returns true if the <html> element should be exposed to the
-// browser process AXTree (as an ignored node).
-AX_BASE_EXPORT bool IsAccessibilityExposeHTMLElementEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityExposeIgnoredNodes);
-
-// Returns true if all ignored nodes are exposed by Blink in the
-// accessibility tree.
-AX_BASE_EXPORT bool IsAccessibilityExposeIgnoredNodesEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityLanguageDetection);
-
-// Return true if language detection should be used to determine the language
-// of text content in page and exposed to the browser process AXTree.
-AX_BASE_EXPORT bool IsAccessibilityLanguageDetectionEnabled();
-
-// Serializes accessibility information from the Views tree and deserializes it
-// into an AXTree in the browser process.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityTreeForViews);
-
-// Returns true if the Views tree is exposed using an AXTree in the browser
-// process. Returns false if the Views tree is exposed to accessibility
-// directly.
-AX_BASE_EXPORT bool IsAccessibilityTreeForViewsEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityRestrictiveIA2AXModes);
-
-// Returns true if the more restrictive approach that only enables the web
-// content related AXModes on an IA2 query when the data is being queried on an
-// web content node.
-//
-// TODO(1441211): Remove flag once the change has been confirmed safe.
-AX_BASE_EXPORT bool IsAccessibilityRestrictiveIA2AXModesEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityFocusHighlight);
-
-// Returns true if the accessibility focus highlight feature is enabled,
-// which draws a visual highlight around the focused element on the page
+// Draw a visual highlight around the focused element on the page
 // briefly whenever focus changes.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityFocusHighlight);
 AX_BASE_EXPORT bool IsAccessibilityFocusHighlightEnabled();
 
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAutoDisableAccessibility);
+// Augment existing images labels in addition to unlabeled images.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAugmentExistingImageLabels);
+AX_BASE_EXPORT bool IsAugmentExistingImageLabelsEnabled();
 
-// Returns true if accessibility will be auto-disabled after a certain
+// Disable the accessibility engine after a certain
 // number of user input events spanning a minimum amount of time with no
 // accessibility API usage in that time.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAutoDisableAccessibility);
 AX_BASE_EXPORT bool IsAutoDisableAccessibilityEnabled();
 
-// Enables a setting that can turn on/off browser vocalization of 'descriptions'
-// tracks.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kTextBasedAudioDescription);
+// Make PDFs displayed in the ChromeOS Media App (AKA Backlight)
+// accessible by performing OCR on the images for each page.
+// TODO(nektar): Should this be moved to ChromeOS section?
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kBacklightOcr);
+AX_BASE_EXPORT bool IsBacklightOcrEnabled();
 
-// Returns true if the setting to turn on text based audio descriptions is
-// enabled.
+// Recognize "aria-virtualcontent" as a valid aria property.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityAriaVirtualContent);
+AX_BASE_EXPORT bool IsAccessibilityAriaVirtualContentEnabled();
+
+// Expose the <html> element to the browser process AXTree (as an
+// ignored node).
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityExposeHTMLElement);
+AX_BASE_EXPORT bool IsAccessibilityExposeHTMLElementEnabled();
+
+// Expose all ignored nodes by Blink in the accessibility tree.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityExposeIgnoredNodes);
+AX_BASE_EXPORT bool IsAccessibilityExposeIgnoredNodesEnabled();
+
+// Use language detection to determine the language
+// of text content in page and exposed to the browser process AXTree.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityLanguageDetection);
+AX_BASE_EXPORT bool IsAccessibilityLanguageDetectionEnabled();
+
+// Restrict AXModes to web content related modes only when an IA2
+// query is performed on a web content node.
+// TODO(1441211): Remove flag once the change has been confirmed safe.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityRestrictiveIA2AXModes);
+AX_BASE_EXPORT bool IsAccessibilityRestrictiveIA2AXModesEnabled();
+
+// Serialize accessibility information from the Views tree and
+// deserialize it into an AXTree in the browser process.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAccessibilityTreeForViews);
+AX_BASE_EXPORT bool IsAccessibilityTreeForViewsEnabled();
+
+// Support aria element reflection. For example:
+//     element.ariaActiveDescendantElement = child;
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAriaElementReflection);
+AX_BASE_EXPORT bool IsAriaElementReflectionEnabled();
+
+// Turn on browser vocalization of 'descriptions' tracks.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kTextBasedAudioDescription);
 AX_BASE_EXPORT bool IsTextBasedAudioDescriptionEnabled();
 
-#if BUILDFLAG(IS_WIN)
-// Enables an experimental Chrome-specific accessibility COM API
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kIChromeAccessible);
+// Expose document markers on inline text boxes in addition to
+// static nodes. (Note: This will make it possible for AXPosition in the browser
+// process to handle document markers, which will be platform agnositc)
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kUseAXPositionForDocumentMarkers);
+AX_BASE_EXPORT bool IsUseAXPositionForDocumentMarkersEnabled();
 
-// Returns true if the IChromeAccessible COM API is enabled.
+#if BUILDFLAG(IS_WIN)
+// Use Chrome-specific accessibility COM API.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kIChromeAccessible);
 AX_BASE_EXPORT bool IsIChromeAccessibleEnabled();
 
+// Selectively enable accessibility depending on the
+// UIA APIs that are called. (Note: This will make it possible for
+// non-screenreader services to enable less of the accessibility system)
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(kSelectiveUIAEnablement);
-
-// Returns true if accessibility will be selectively enabled depending on the
-// UIA APIs that are called, allowing non-screenreader usage to enable less of
-// the accessibility system.
 AX_BASE_EXPORT bool IsSelectiveUIAEnablementEnabled();
 
+// Use the browser's UIA provider when requested by
+// an accessibility client.
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(kUiaProvider);
-
-// Returns true if the browser's UIA provider should be used when requested by
-// an a11y client.
 AX_BASE_EXPORT bool IsUiaProviderEnabled();
 #endif  // BUILDFLAG(IS_WIN)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+// TODO(accessibility): Should this be moved to ash_features.cc?
 AX_BASE_EXPORT bool IsDictationOfflineAvailable();
 
-// Enables Context Checking with the accessibility Dictation feature.
+// Allow accessibility accelerator notifications to timeout.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(
+    kAccessibilityAcceleratorNotificationsTimeout);
+AX_BASE_EXPORT bool IsAccessibilityAcceleratorNotificationsTimeoutEnabled();
+
+// Use Dictation keyboard improvements.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(
+    kAccessibilityDictationKeyboardImprovements);
+AX_BASE_EXPORT bool IsAccessibilityDictationKeyboardImprovementsEnabled();
+
+// Integrate with FaceGaze.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityFaceGaze);
+AX_BASE_EXPORT bool IsAccessibilityFaceGazeEnabled();
+
+// Use Select-to-Speak hover text improvements.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(
+    kAccessibilitySelectToSpeakHoverTextImprovements);
+AX_BASE_EXPORT bool IsAccessibilitySelectToSpeakHoverTextImprovementsEnabled();
+
+// Allow context checking with the accessibility Dictation
+// feature.
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(
     kExperimentalAccessibilityDictationContextChecking);
-
-// Returns true if Dictation with context checking is enabled.
 AX_BASE_EXPORT bool
 IsExperimentalAccessibilityDictationContextCheckingEnabled();
 
-// Enables downloading Google TTS voices using Language Packs.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(
-    kExperimentalAccessibilityGoogleTtsLanguagePacks);
-
-// Returns true if using Language Packs to download Google TTS voices is
-// enabled.
-AX_BASE_EXPORT bool IsExperimentalAccessibilityGoogleTtsLanguagePacksEnabled();
-
-// Enables downloading Google TTS High Quality voices.
+// Download Google TTS High Quality voices.
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(
     kExperimentalAccessibilityGoogleTtsHighQualityVoices);
-
-// Returns true if downloading High Quality Google TTS voices is enabled.
 AX_BASE_EXPORT bool
 IsExperimentalAccessibilityGoogleTtsHighQualityVoicesEnabled();
 
-// Enables Dictation keyboard improvements.
+// Use Language Packs to download Google TTS voices.
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(
-    kAccessibilityDictationKeyboardImprovements);
-
-// Returns true if Dictation keyboard improvements are enabled.
-AX_BASE_EXPORT bool IsAccessibilityDictationKeyboardImprovementsEnabled();
-
-// Enables AccessibilitySelectToSpeakHoverTextImprovements.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(
-    kAccessibilitySelectToSpeakHoverTextImprovements);
-
-// Returns true if AccessibilitySelectToSpeakHoverTextImprovements is enabled.
-AX_BASE_EXPORT bool IsAccessibilitySelectToSpeakHoverTextImprovementsEnabled();
-
-// Enables accessibility accelerator notifications to timeout.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(
-    kAccessibilityAcceleratorNotificationsTimeout);
-
-// Returns true if kAccessibilityAcceleratorNotificationsTimeout is enabled.
-AX_BASE_EXPORT bool IsAccessibilityAcceleratorNotificationsTimeoutEnabled();
-
-// Enables the experimental FaceGaze integration.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityFaceGaze);
-
-// Returns true if the FaceGaze integration is enabled.
-AX_BASE_EXPORT bool IsAccessibilityFaceGazeEnabled();
+    kExperimentalAccessibilityGoogleTtsLanguagePacks);
+AX_BASE_EXPORT bool IsExperimentalAccessibilityGoogleTtsLanguagePacksEnabled();
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-// A feature that makes PDFs displayed in the ChromeOS Media App (AKA Backlight)
-// accessible by performing OCR on the images for each page.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kBacklightOcr);
-AX_BASE_EXPORT bool IsBacklightOcrEnabled();
-
-// Enables Get Image Descriptions to augment existing images labels,
-// rather than only provide descriptions for completely unlabeled images.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAugmentExistingImageLabels);
-
-// Returns true if augmenting existing image labels is enabled.
-AX_BASE_EXPORT bool IsAugmentExistingImageLabelsEnabled();
-
-// Once this flag is enabled, a single codebase in AXPosition will be used for
-// handling document markers on all platforms, including the announcement of
-// spelling mistakes.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kUseAXPositionForDocumentMarkers);
-
-// Returns true if document markers are exposed on inline text boxes in the
-// accessibility tree in addition to on static text nodes. This in turn enables
-// AXPosition on the browser to discover and work with document markers, instead
-// of the legacy code that collects document markers manually from static text
-// nodes and which is different for each platform.
-AX_BASE_EXPORT bool IsUseAXPositionForDocumentMarkersEnabled();
-
-// Enable support for ARIA element reflection, for example
-// element.ariaActiveDescendantElement = child;
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kEnableAriaElementReflection);
-
-// Returns true if ARIA element reflection is enabled.
-AX_BASE_EXPORT bool IsAriaElementReflectionEnabled();
-
-// Experiment to increase the cost of SendPendingAccessibilityEvents.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAblateSendPendingAccessibilityEvents);
-
-// Returns true if |kAblateSendPendingAccessibilityEvents| is enabled.
-AX_BASE_EXPORT bool IsAblateSendPendingAccessibilityEventsEnabled();
-
 #if BUILDFLAG(IS_ANDROID)
-// Enable filtered AXModes based on running services. If disabled, then AXModes
-// will not be available to be set.
+// Filter AXModes based on running accessibility services.
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityPerformanceFiltering);
-
-// Returns true if AXMode filtering for performance is enabled.
 AX_BASE_EXPORT bool IsAccessibilityPerformanceFilteringEnabled();
 
-// When enabled, this setting will disable max node and timeout limits on the
+// Disable max node and timeout limits on the
 // AXTreeSnapshotter's Snapshot method, and track related histograms.
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilitySnapshotStressTests);
 AX_BASE_EXPORT bool IsAccessibilitySnapshotStressTestsEnabled();
-
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if !BUILDFLAG(IS_ANDROID)
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnything);
-
-// Returns true if read anything is enabled. This feature shows users websites,
-// such as articles, in a comfortable reading experience in a side panel.
-AX_BASE_EXPORT bool IsReadAnythingEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnythingWithScreen2x);
-
-// Returns true if read anything is enabled with screen2x integration, which
-// distills web pages using an ML model.
-AX_BASE_EXPORT bool IsReadAnythingWithScreen2xEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kDataCollectionModeForScreen2x);
-
-// If enabled, the browser will open with read_anything open in the side panel,
-// and calls distill only once we receive navigation's load-complete event.
-// This is because the browser is only being opened to render one webpage, for
-// the sake of generating training data for Screen2x data collection. The
-// browser is intended to be closed by the user who launches Chrome once the
-// first distill call finishes executing. This feature should be used along
-// with 'ScreenAIDebugModeEnabled=true' and --no-sandbox.
-AX_BASE_EXPORT bool IsDataCollectionModeForScreen2xEnabled();
-
-// If enabled, ScreenAI library writes some debug data in /tmp.
-AX_BASE_EXPORT bool IsScreenAIDebugModeEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnythingWebUIToolbar);
-
-// If enabled, use the WebUI toolbar in Read Anything.
-AX_BASE_EXPORT bool IsReadAnythingWebUIToolbarEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnythingReadAloud);
-
-// If enabled, show the Read Aloud feature in Read Anything.
-AX_BASE_EXPORT bool IsReadAnythingReadAloudEnabled();
-
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnythingOmniboxIcon);
-
-// If enabled, show a reading mode icon in the omnibox.
-AX_BASE_EXPORT bool IsReadAnythingOmniboxIconEnabled();
-
-// Enables a feature whereby inaccessible (i.e. untagged) PDFs are made
-// accessible using an optical character recognition service. Due to the size of
-// the OCR component, this feature targets desktop versions of Chrome for now.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kPdfOcr);
-
-// Returns true if OCR will be performed on inaccessible (i.e. untagged) PDFs
-// and the resulting text, together with its layout information, will be added
-// to the accessibility tree.
-AX_BASE_EXPORT bool IsPdfOcrEnabled();
-
-// Enables a feature whereby inaccessible surfaces such as canvases are made
-// accessible using a local machine intelligence service.
-AX_BASE_EXPORT BASE_DECLARE_FEATURE(kLayoutExtraction);
-
-// Returns true if Layout Extraction feature is enabled. This feature uses a
-// local machine intelligence library to process screenshots and adds metadata
-// to the accessibility tree.
-AX_BASE_EXPORT bool IsLayoutExtractionEnabled();
-
-// Enables the experimental Accessibility Service.
+// Use the experimental Accessibility Service.
+// TODO(katydek): Provide a more descriptive name here.
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityService);
-
-// Returns true if the Accessibility Service enabled.
 AX_BASE_EXPORT bool IsAccessibilityServiceEnabled();
 
+// Open Read Anything side panel when the browser is opened, and
+// call distill after the navigation's load-complete event. (Note: The browser
+// is only being opened to render one webpage, for the sake of generating
+// training data for Screen2x data collection. The browser is intended to be
+// closed by the user who launches Chrome once the first distill call finishes
+// executing.)
+//
+// Note: This feature should be used along with 'ScreenAIDebugModeEnabled=true'
+// and --no-sandbox.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kDataCollectionModeForScreen2x);
+AX_BASE_EXPORT bool IsDataCollectionModeForScreen2xEnabled();
+
+// Use local MI service to make inaccessibile surfaces (e.g.
+// canvases) accessible.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kLayoutExtraction);
+AX_BASE_EXPORT bool IsLayoutExtractionEnabled();
+
+// Use OCR to make inaccessible (i.e. untagged) PDFs
+// accessibility. (Note: Due to the size of the OCR component, this feature
+// targets only desktop versions of Chrome for now.)
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kPdfOcr);
+AX_BASE_EXPORT bool IsPdfOcrEnabled();
+
+// Include the Read Anything feature. (Note: This feature shows
+// users websites, such as articles, in a comfortable reading experience in a
+// side panel)
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnything);
+AX_BASE_EXPORT bool IsReadAnythingEnabled();
+
+// Show a reading mode icon in the omnibox.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnythingOmniboxIcon);
+AX_BASE_EXPORT bool IsReadAnythingOmniboxIconEnabled();
+
+// Show the Read Aloud feature in Read Anything.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnythingReadAloud);
+AX_BASE_EXPORT bool IsReadAnythingReadAloudEnabled();
+
+// Use the WebUI toolbar in Read Anything.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnythingWebUIToolbar);
+AX_BASE_EXPORT bool IsReadAnythingWebUIToolbarEnabled();
+
+// Use screen2x integration for Read Anything to distill web pages
+// using an ML model.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kReadAnythingWithScreen2x);
+AX_BASE_EXPORT bool IsReadAnythingWithScreen2xEnabled();
+
+// Write some ScreenAI library debug data in /tmp.
+AX_BASE_EXPORT BASE_DECLARE_FEATURE(kScreenAIDebugMode);
+AX_BASE_EXPORT bool IsScreenAIDebugModeEnabled();
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_MAC)
-// Enables the NSAccessibilityRemoteUIElement's RemoteUIApp. We need to set
-// NSAccessibilityRemoteUIElement's RemoteUIApp to YES, which is to fix some
-// a11y bugs in PWA Mac.
-// It is a temporary feature flag, and will be removed once browser with this
-// feature enabled can run stably. The reason we're so careful is because a
-// previous CL that enabling NSAccessibilityRemoteUIElement's RemoteUIApp caused
-// chromium to hang. With the feature flag, once chromium encounters a bug due
-// to this we can urgently disable it. See https://crbug.com/1491329
+// Set NSAccessibilityRemoteUIElement's RemoteUIApp to YES to fix
+// some accessibility bugs in PWA Mac. (Note: When enabling
+// NSAccessibilityRemoteUIElement's RemoteUIApp previously, chromium would hang.
+// See: https://crbug.com/1491329).
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityRemoteUIApp);
-
-// Returns true if the NSAccessibilityRemoteUIElement's RemoteUIApp is enabled.
 AX_BASE_EXPORT bool IsAccessibilityRemoteUIAppEnabled();
-
 #endif  // BUILDFLAG(IS_MAC)
 
 }  // namespace features
diff --git a/ui/display/manager/BUILD.gn b/ui/display/manager/BUILD.gn
index 8b47604b..1dd366d9 100644
--- a/ui/display/manager/BUILD.gn
+++ b/ui/display/manager/BUILD.gn
@@ -56,6 +56,7 @@
       "display_layout_store.h",
       "display_manager.cc",
       "display_manager.h",
+      "display_manager_observer.cc",
       "display_manager_observer.h",
       "display_port_observer.cc",
       "display_port_observer.h",
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index 526adeb..35202de 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -1156,24 +1156,21 @@
   if (has_pending_power_state_)
     return true;
 
-  // Schedule if there is a request to change the VRR enabled state.
-  if (ShouldConfigureVrr()) {
-    return true;
-  }
-
-  // TODO(b/221220344): Remove after seamless modesets are fixed.
-  // Schedule if the conditions for seamless configuration are met and VRR is
-  // currently enabled on the internal display.
-  if (HasPendingSeamlessConfiguration() && IsVrrEnabledOnInternalDisplay()) {
-    return true;
-  }
-
   return false;
 }
 
 bool DisplayConfigurator::HasPendingSeamlessConfiguration() const {
   // Schedule if there is a pending request to change the refresh rate.
-  return pending_refresh_rate_throttle_state_.has_value();
+  if (pending_refresh_rate_throttle_state_.has_value()) {
+    return true;
+  }
+
+  // Schedule if there is a request to change the VRR enabled state.
+  if (ShouldConfigureVrr()) {
+    return true;
+  }
+
+  return false;
 }
 
 void DisplayConfigurator::CallAndClearInProgressCallbacks(bool success) {
@@ -1241,18 +1238,4 @@
   return false;
 }
 
-bool DisplayConfigurator::IsVrrEnabledOnInternalDisplay() const {
-  const DisplaySnapshot* internal_display;
-  for (const auto* display : cached_displays_) {
-    if (display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL) {
-      internal_display = display;
-      break;
-    }
-  }
-
-  return internal_display != nullptr &&
-         internal_display->current_mode() != nullptr &&
-         internal_display->IsVrrEnabled();
-}
-
 }  // namespace display
diff --git a/ui/display/manager/display_configurator.h b/ui/display/manager/display_configurator.h
index 755114c..32ce4de 100644
--- a/ui/display/manager/display_configurator.h
+++ b/ui/display/manager/display_configurator.h
@@ -390,10 +390,6 @@
   // request.
   bool ShouldConfigureVrr() const;
 
-  // Returns whether variable refresh rates are enabled on the internal display
-  // (if there is one).
-  bool IsVrrEnabledOnInternalDisplay() const;
-
   raw_ptr<StateController> state_controller_;
   raw_ptr<SoftwareMirroringController> mirroring_controller_;
   std::unique_ptr<NativeDisplayDelegate> native_display_delegate_;
diff --git a/ui/display/manager/display_configurator_unittest.cc b/ui/display/manager/display_configurator_unittest.cc
index 1e59156b..aa36a6c0 100644
--- a/ui/display/manager/display_configurator_unittest.cc
+++ b/ui/display/manager/display_configurator_unittest.cc
@@ -253,37 +253,41 @@
     configurator_.set_state_controller(&state_controller_);
     configurator_.set_mirroring_controller(&mirroring_controller_);
 
-    outputs_[0] = FakeDisplaySnapshot::Builder()
-                      .SetId(kDisplayIds[0])
-                      .SetNativeMode(small_mode_.Clone())
-                      .SetCurrentMode(small_mode_.Clone())
-                      .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
-                      .SetBaseConnectorId(kEdpConnectorId)
-                      .SetIsAspectPreservingScaling(true)
-                      .SetVariableRefreshRateState(kVrrDisabled)
-                      .SetVsyncRateMin(40)
-                      .Build();
+    owned_outputs_[0] = FakeDisplaySnapshot::Builder()
+                            .SetId(kDisplayIds[0])
+                            .SetNativeMode(small_mode_.Clone())
+                            .SetCurrentMode(small_mode_.Clone())
+                            .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
+                            .SetBaseConnectorId(kEdpConnectorId)
+                            .SetIsAspectPreservingScaling(true)
+                            .SetVariableRefreshRateState(kVrrDisabled)
+                            .SetVsyncRateMin(40)
+                            .Build();
 
-    outputs_[1] = FakeDisplaySnapshot::Builder()
-                      .SetId(kDisplayIds[1])
-                      .SetNativeMode(big_mode_.Clone())
-                      .SetCurrentMode(big_mode_.Clone())
-                      .AddMode(small_mode_.Clone())
-                      .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
-                      .SetBaseConnectorId(kSecondConnectorId)
-                      .SetIsAspectPreservingScaling(true)
-                      .SetVariableRefreshRateState(kVrrNotCapable)
-                      .Build();
+    owned_outputs_[1] = FakeDisplaySnapshot::Builder()
+                            .SetId(kDisplayIds[1])
+                            .SetNativeMode(big_mode_.Clone())
+                            .SetCurrentMode(big_mode_.Clone())
+                            .AddMode(small_mode_.Clone())
+                            .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
+                            .SetBaseConnectorId(kSecondConnectorId)
+                            .SetIsAspectPreservingScaling(true)
+                            .SetVariableRefreshRateState(kVrrNotCapable)
+                            .Build();
 
-    outputs_[2] = FakeDisplaySnapshot::Builder()
-                      .SetId(kDisplayIds[2])
-                      .SetNativeMode(small_mode_.Clone())
-                      .SetCurrentMode(small_mode_.Clone())
-                      .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
-                      .SetBaseConnectorId(kThirdConnectorId)
-                      .SetIsAspectPreservingScaling(true)
-                      .SetVariableRefreshRateState(kVrrNotCapable)
-                      .Build();
+    owned_outputs_[2] = FakeDisplaySnapshot::Builder()
+                            .SetId(kDisplayIds[2])
+                            .SetNativeMode(small_mode_.Clone())
+                            .SetCurrentMode(small_mode_.Clone())
+                            .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
+                            .SetBaseConnectorId(kThirdConnectorId)
+                            .SetIsAspectPreservingScaling(true)
+                            .SetVariableRefreshRateState(kVrrNotCapable)
+                            .Build();
+
+    for (size_t i = 0; i < kNumOutputs; ++i) {
+      outputs_[i] = owned_outputs_[i].get();
+    }
 
     UpdateOutputs(2, false);
   }
@@ -307,8 +311,9 @@
   void UpdateOutputs(size_t num_outputs, bool send_events) {
     ASSERT_LE(num_outputs, std::size(outputs_));
     std::vector<DisplaySnapshot*> outputs;
-    for (size_t i = 0; i < num_outputs; ++i)
-      outputs.push_back(outputs_[i].get());
+    for (size_t i = 0; i < num_outputs; ++i) {
+      outputs.push_back(outputs_[i]);
+    }
     native_display_delegate_->set_outputs(outputs);
 
     if (send_events) {
@@ -373,7 +378,7 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 
   static constexpr size_t kNumOutputs = 3;
-  std::unique_ptr<DisplaySnapshot> outputs_[kNumOutputs];
+  DisplaySnapshot* outputs_[kNumOutputs];
 
   CallbackResult display_control_result_ = CALLBACK_NOT_CALLED;
 
@@ -401,6 +406,10 @@
     return rest.empty() ? action
                         : JoinActions(action.c_str(), rest.c_str(), nullptr);
   }
+
+  // These snapshots must outlive their usage in |outputs_| because they may
+  // still be referenced by the DisplayConfigurator cache.
+  std::unique_ptr<DisplaySnapshot> owned_outputs_[kNumOutputs];
 };
 
 }  // namespace
@@ -516,13 +525,14 @@
   EXPECT_EQ(1, observer_.num_changes());
 
   // Get rid of shared modes to force software mirroring.
-  outputs_[1] = FakeDisplaySnapshot::Builder()
+  auto output = FakeDisplaySnapshot::Builder()
                     .SetId(kDisplayIds[1])
                     .SetNativeMode(big_mode_.Clone())
                     .SetCurrentMode(big_mode_.Clone())
                     .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                     .SetIsAspectPreservingScaling(true)
                     .Build();
+  outputs_[1] = output.get();
 
   state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
   UpdateOutputs(2, true);
@@ -652,13 +662,14 @@
   EXPECT_EQ(1, observer_.num_changes());
 
   // Get rid of shared modes to force software mirroring.
-  outputs_[1] = FakeDisplaySnapshot::Builder()
+  auto output = FakeDisplaySnapshot::Builder()
                     .SetId(kDisplayIds[1])
                     .SetNativeMode(big_mode_.Clone())
                     .SetCurrentMode(big_mode_.Clone())
                     .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                     .SetIsAspectPreservingScaling(true)
                     .Build();
+  outputs_[1] = output.get();
 
   state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
   observer_.Reset();
@@ -891,7 +902,7 @@
   EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
 
   // Connect an external display and check that it's configured correctly.
-  outputs_[0] = FakeDisplaySnapshot::Builder()
+  auto output = FakeDisplaySnapshot::Builder()
                     .SetId(kDisplayIds[0])
                     .SetNativeMode(big_mode_.Clone())
                     .SetCurrentMode(big_mode_.Clone())
@@ -899,6 +910,7 @@
                     .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                     .SetIsAspectPreservingScaling(true)
                     .Build();
+  outputs_[0] = output.get();
 
   UpdateOutputs(1, true);
   EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&big_mode_).c_str(),
@@ -1017,21 +1029,24 @@
 
   // Initialize with 3 displays where the internal panel is not at the top of
   // the display list.
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(1L)
-                    .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
-                    .SetNativeMode(big_mode_.Clone())
-                    .Build();
-  outputs_[1] = FakeDisplaySnapshot::Builder()
-                    .SetId(2L)
-                    .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
-                    .SetNativeMode(small_mode_.Clone())
-                    .Build();
-  outputs_[2] = FakeDisplaySnapshot::Builder()
-                    .SetId(3L)
-                    .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
-                    .SetNativeMode(big_mode_.Clone())
-                    .Build();
+  auto output0 = FakeDisplaySnapshot::Builder()
+                     .SetId(1L)
+                     .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
+                     .SetNativeMode(big_mode_.Clone())
+                     .Build();
+  outputs_[0] = output0.get();
+  auto output1 = FakeDisplaySnapshot::Builder()
+                     .SetId(2L)
+                     .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
+                     .SetNativeMode(small_mode_.Clone())
+                     .Build();
+  outputs_[1] = output1.get();
+  auto output2 = FakeDisplaySnapshot::Builder()
+                     .SetId(3L)
+                     .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
+                     .SetNativeMode(big_mode_.Clone())
+                     .Build();
+  outputs_[2] = output2.get();
 
   native_display_delegate_->set_max_configurable_pixels(
       big_mode_.size().GetArea());
@@ -1158,18 +1173,19 @@
   modes.push_back(MakeDisplayMode(1920, 1080, false, 60.0));
   modes.push_back(MakeDisplayMode(1920, 1080, false, 40.0));
 
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[0])
-                    .SetNativeMode(modes[0]->Clone())
-                    .SetCurrentMode(modes[0]->Clone())
-                    .AddMode(modes[1]->Clone())
-                    .AddMode(modes[2]->Clone())
-                    .AddMode(modes[3]->Clone())
-                    .AddMode(modes[4]->Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
-                    .SetBaseConnectorId(kEdpConnectorId)
-                    .SetIsAspectPreservingScaling(true)
-                    .Build();
+  auto output0 = FakeDisplaySnapshot::Builder()
+                     .SetId(kDisplayIds[0])
+                     .SetNativeMode(modes[0]->Clone())
+                     .SetCurrentMode(modes[0]->Clone())
+                     .AddMode(modes[1]->Clone())
+                     .AddMode(modes[2]->Clone())
+                     .AddMode(modes[3]->Clone())
+                     .AddMode(modes[4]->Clone())
+                     .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
+                     .SetBaseConnectorId(kEdpConnectorId)
+                     .SetIsAspectPreservingScaling(true)
+                     .Build();
+  outputs_[0] = output0.get();
 
   // Since Chrome restricts the internal display to its native mode it should
   // not attempt other available modes. The likelihood of an internal display
@@ -1195,18 +1211,19 @@
                 kModesetOutcomeFailure, nullptr),
             log_->GetActionsAndClear());
 
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[0])
-                    .SetNativeMode(modes[0]->Clone())
-                    .SetCurrentMode(modes[0]->Clone())
-                    .AddMode(modes[1]->Clone())
-                    .AddMode(modes[2]->Clone())
-                    .AddMode(modes[3]->Clone())
-                    .AddMode(modes[4]->Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
-                    .SetBaseConnectorId(kEdpConnectorId)
-                    .SetIsAspectPreservingScaling(true)
-                    .Build();
+  auto output1 = FakeDisplaySnapshot::Builder()
+                     .SetId(kDisplayIds[0])
+                     .SetNativeMode(modes[0]->Clone())
+                     .SetCurrentMode(modes[0]->Clone())
+                     .AddMode(modes[1]->Clone())
+                     .AddMode(modes[2]->Clone())
+                     .AddMode(modes[3]->Clone())
+                     .AddMode(modes[4]->Clone())
+                     .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
+                     .SetBaseConnectorId(kEdpConnectorId)
+                     .SetIsAspectPreservingScaling(true)
+                     .Build();
+  outputs_[0] = output1.get();
 
   // This test simply fails in MULTIPLE_DISPLAY_STATE_SINGLE mode for an
   // external display (assuming the internal display is disabled; e.g. the lid
@@ -1248,31 +1265,33 @@
                 kModesetOutcomeSuccess, nullptr),
             log_->GetActionsAndClear());
 
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[0])
-                    .SetNativeMode(modes[0]->Clone())
-                    .SetCurrentMode(modes[0]->Clone())
-                    .AddMode(modes[1]->Clone())
-                    .AddMode(modes[2]->Clone())
-                    .AddMode(modes[3]->Clone())
-                    .AddMode(modes[4]->Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
-                    .SetBaseConnectorId(kEdpConnectorId)
-                    .SetIsAspectPreservingScaling(true)
-                    .Build();
+  auto output2 = FakeDisplaySnapshot::Builder()
+                     .SetId(kDisplayIds[0])
+                     .SetNativeMode(modes[0]->Clone())
+                     .SetCurrentMode(modes[0]->Clone())
+                     .AddMode(modes[1]->Clone())
+                     .AddMode(modes[2]->Clone())
+                     .AddMode(modes[3]->Clone())
+                     .AddMode(modes[4]->Clone())
+                     .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
+                     .SetBaseConnectorId(kEdpConnectorId)
+                     .SetIsAspectPreservingScaling(true)
+                     .Build();
+  outputs_[0] = output2.get();
 
-  outputs_[1] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[1])
-                    .SetNativeMode(modes[0]->Clone())
-                    .SetCurrentMode(modes[0]->Clone())
-                    .AddMode(modes[1]->Clone())
-                    .AddMode(modes[2]->Clone())
-                    .AddMode(modes[3]->Clone())
-                    .AddMode(modes[4]->Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
-                    .SetBaseConnectorId(kSecondConnectorId)
-                    .SetIsAspectPreservingScaling(true)
-                    .Build();
+  auto output3 = FakeDisplaySnapshot::Builder()
+                     .SetId(kDisplayIds[1])
+                     .SetNativeMode(modes[0]->Clone())
+                     .SetCurrentMode(modes[0]->Clone())
+                     .AddMode(modes[1]->Clone())
+                     .AddMode(modes[2]->Clone())
+                     .AddMode(modes[3]->Clone())
+                     .AddMode(modes[4]->Clone())
+                     .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
+                     .SetBaseConnectorId(kSecondConnectorId)
+                     .SetIsAspectPreservingScaling(true)
+                     .Build();
+  outputs_[1] = output3.get();
 
   // This test should attempt to configure a mirror mode that will not succeed
   // and should end up in extended mode.
@@ -1989,7 +2008,7 @@
   std::vector<std::unique_ptr<const DisplayMode>> modes;
   modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
   modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
-  outputs_[0] = FakeDisplaySnapshot::Builder()
+  auto output = FakeDisplaySnapshot::Builder()
                     .SetId(kDisplayIds[0])
                     .SetNativeMode(modes[0]->Clone())
                     .SetCurrentMode(modes[0]->Clone())
@@ -1998,6 +2017,7 @@
                     .SetBaseConnectorId(kEdpConnectorId)
                     .SetIsAspectPreservingScaling(true)
                     .Build();
+  outputs_[0] = output.get();
   state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
   UpdateOutputs(1, true);
   EXPECT_EQ(120.0f, outputs_[0]->current_mode()->refresh_rate());
@@ -2054,25 +2074,27 @@
   std::vector<std::unique_ptr<const DisplayMode>> modes;
   modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
   modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[0])
-                    .SetNativeMode(modes[0]->Clone())
-                    .SetCurrentMode(modes[0]->Clone())
-                    .AddMode(modes[1]->Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
-                    .SetBaseConnectorId(kEdpConnectorId)
-                    .SetIsAspectPreservingScaling(true)
-                    .Build();
+  auto output0 = FakeDisplaySnapshot::Builder()
+                     .SetId(kDisplayIds[0])
+                     .SetNativeMode(modes[0]->Clone())
+                     .SetCurrentMode(modes[0]->Clone())
+                     .AddMode(modes[1]->Clone())
+                     .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
+                     .SetBaseConnectorId(kEdpConnectorId)
+                     .SetIsAspectPreservingScaling(true)
+                     .Build();
+  outputs_[0] = output0.get();
   // External display should never be throttled irregardless of its modes.
-  outputs_[1] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[1])
-                    .SetNativeMode(modes[0]->Clone())
-                    .SetCurrentMode(modes[0]->Clone())
-                    .AddMode(modes[1]->Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
-                    .SetBaseConnectorId(kSecondConnectorId)
-                    .SetIsAspectPreservingScaling(true)
-                    .Build();
+  auto output1 = FakeDisplaySnapshot::Builder()
+                     .SetId(kDisplayIds[1])
+                     .SetNativeMode(modes[0]->Clone())
+                     .SetCurrentMode(modes[0]->Clone())
+                     .AddMode(modes[1]->Clone())
+                     .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
+                     .SetBaseConnectorId(kSecondConnectorId)
+                     .SetIsAspectPreservingScaling(true)
+                     .Build();
+  outputs_[1] = output1.get();
   state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
   UpdateOutputs(2, true);
   EXPECT_EQ(120.0f, outputs_[0]->current_mode()->refresh_rate());
@@ -2163,7 +2185,7 @@
   int vertical_offset = outputs_[0]->native_mode()->size().height() +
                         DisplayConfigurator::kVerticalGap;
   EXPECT_EQ(JoinActions(
-                kTestModesetStr,
+                kTestModesetStr, kSeamlessModesetStr,
                 GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
                                outputs_[0]->native_mode(), /*enable_vrr=*/true})
                     .c_str(),
@@ -2171,7 +2193,7 @@
                     {outputs_[1]->display_id(), gfx::Point(0, vertical_offset),
                      outputs_[1]->native_mode(), /*enable_vrr=*/false})
                     .c_str(),
-                kModesetOutcomeSuccess, kCommitModesetStr,
+                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
                 GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
                                outputs_[0]->native_mode(), /*enable_vrr=*/true})
                     .c_str(),
@@ -2190,7 +2212,7 @@
   EXPECT_FALSE(outputs_[1]->IsVrrEnabled());
   EXPECT_EQ(
       JoinActions(
-          kTestModesetStr,
+          kTestModesetStr, kSeamlessModesetStr,
           GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
                          outputs_[0]->native_mode(), /*enable_vrr=*/false})
               .c_str(),
@@ -2198,7 +2220,7 @@
                          gfx::Point(0, vertical_offset),
                          outputs_[1]->native_mode(), /*enable_vrr=*/false})
               .c_str(),
-          kModesetOutcomeSuccess, kCommitModesetStr,
+          kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
           GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
                          outputs_[0]->native_mode(), /*enable_vrr=*/false})
               .c_str(),
@@ -2232,7 +2254,7 @@
   std::vector<std::unique_ptr<const DisplayMode>> modes;
   modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
   modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
-  outputs_[0] = FakeDisplaySnapshot::Builder()
+  auto output = FakeDisplaySnapshot::Builder()
                     .SetId(kDisplayIds[0])
                     .SetNativeMode(modes[0]->Clone())
                     .SetCurrentMode(modes[0]->Clone())
@@ -2243,6 +2265,7 @@
                     .SetVariableRefreshRateState(kVrrDisabled)
                     .SetVsyncRateMin(40)
                     .Build();
+  outputs_[0] = output.get();
   state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
   UpdateOutputs(1, true);
   // Enable VRR on internal display.
@@ -2257,19 +2280,19 @@
                                                  kRefreshRateThrottleEnabled);
   EXPECT_EQ(60.0f, outputs_[0]->current_mode()->refresh_rate());
   EXPECT_EQ(1, observer_.num_changes());
-  // As long as VRR is enabled, throttling should trigger full (not seamless)
-  // modesets.
-  EXPECT_EQ(
-      JoinActions(kTestModesetStr,
-                  GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
-                                 modes[1].get(), /*enable_vrr=*/true})
-                      .c_str(),
-                  kModesetOutcomeSuccess, kCommitModesetStr,
-                  GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
-                                 modes[1].get(), /*enable_vrr=*/true})
-                      .c_str(),
-                  kModesetOutcomeSuccess, nullptr),
-      log_->GetActionsAndClear());
+  // Throttling should be unaffected by the internal display VRR state and still
+  // result in seamless modesets.
+  EXPECT_EQ(JoinActions(
+                kTestModesetStr, kSeamlessModesetStr,
+                GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
+                               modes[1].get(), /*enable_vrr=*/true})
+                    .c_str(),
+                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
+                GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
+                               modes[1].get(), /*enable_vrr=*/true})
+                    .c_str(),
+                kModesetOutcomeSuccess, nullptr),
+            log_->GetActionsAndClear());
   observer_.Reset();
 
   // Set throttle state disabled.
@@ -2277,19 +2300,19 @@
                                                  kRefreshRateThrottleDisabled);
   EXPECT_EQ(120.0f, outputs_[0]->current_mode()->refresh_rate());
   EXPECT_EQ(1, observer_.num_changes());
-  // As long as VRR is enabled, unthrottling should trigger full (not seamless)
-  // modesets.
-  EXPECT_EQ(
-      JoinActions(kTestModesetStr,
-                  GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
-                                 modes[0].get(), /*enable_vrr=*/true})
-                      .c_str(),
-                  kModesetOutcomeSuccess, kCommitModesetStr,
-                  GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
-                                 modes[0].get(), /*enable_vrr=*/true})
-                      .c_str(),
-                  kModesetOutcomeSuccess, nullptr),
-      log_->GetActionsAndClear());
+  // Unthrottling should be unaffected by the internal display VRR state and
+  // still result in seamless modesets.
+  EXPECT_EQ(JoinActions(
+                kTestModesetStr, kSeamlessModesetStr,
+                GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
+                               modes[0].get(), /*enable_vrr=*/true})
+                    .c_str(),
+                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
+                GetCrtcAction({outputs_[0]->display_id(), gfx::Point(0, 0),
+                               modes[0].get(), /*enable_vrr=*/true})
+                    .c_str(),
+                kModesetOutcomeSuccess, nullptr),
+            log_->GetActionsAndClear());
 }
 
 TEST_F(DisplayConfiguratorTest,
@@ -2300,27 +2323,29 @@
   std::vector<std::unique_ptr<const DisplayMode>> modes;
   modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
   modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[0])
-                    .SetNativeMode(modes[0]->Clone())
-                    .SetCurrentMode(modes[0]->Clone())
-                    .AddMode(modes[1]->Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
-                    .SetBaseConnectorId(kEdpConnectorId)
-                    .SetIsAspectPreservingScaling(true)
-                    .SetVariableRefreshRateState(kVrrNotCapable)
-                    .Build();
-  outputs_[1] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[1])
-                    .SetNativeMode(big_mode_.Clone())
-                    .SetCurrentMode(big_mode_.Clone())
-                    .AddMode(small_mode_.Clone())
-                    .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
-                    .SetBaseConnectorId(kSecondConnectorId)
-                    .SetIsAspectPreservingScaling(true)
-                    .SetVariableRefreshRateState(kVrrDisabled)
-                    .SetVsyncRateMin(40)
-                    .Build();
+  auto output0 = FakeDisplaySnapshot::Builder()
+                     .SetId(kDisplayIds[0])
+                     .SetNativeMode(modes[0]->Clone())
+                     .SetCurrentMode(modes[0]->Clone())
+                     .AddMode(modes[1]->Clone())
+                     .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
+                     .SetBaseConnectorId(kEdpConnectorId)
+                     .SetIsAspectPreservingScaling(true)
+                     .SetVariableRefreshRateState(kVrrNotCapable)
+                     .Build();
+  outputs_[0] = output0.get();
+  auto output1 = FakeDisplaySnapshot::Builder()
+                     .SetId(kDisplayIds[1])
+                     .SetNativeMode(big_mode_.Clone())
+                     .SetCurrentMode(big_mode_.Clone())
+                     .AddMode(small_mode_.Clone())
+                     .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
+                     .SetBaseConnectorId(kSecondConnectorId)
+                     .SetIsAspectPreservingScaling(true)
+                     .SetVariableRefreshRateState(kVrrDisabled)
+                     .SetVsyncRateMin(40)
+                     .Build();
+  outputs_[1] = output1.get();
   state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
   UpdateOutputs(2, true);
   // Enable VRR when only the external display is VRR-capable.
@@ -2446,16 +2471,17 @@
 TEST_F(DisplayConfiguratorMultiMirroringTest,
        FindMirrorModeWithInternalDisplay) {
   // Initialize with one internal display and two external displays.
-  outputs_[0] = FakeDisplaySnapshot::Builder()
-                    .SetId(kDisplayIds[0])
-                    .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
-                    .SetNativeMode(MakeDisplayMode(1920, 1600, false, 60.0))
-                    .AddMode(MakeDisplayMode(1920, 1600, false, 60.0))
-                    .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))
-                    .AddMode(MakeDisplayMode(1920, 1080, true, 60.0))
-                    .AddMode(MakeDisplayMode(1440, 900, true, 60.0))
-                    .Build();
-  outputs_[1] =
+  auto output0 = FakeDisplaySnapshot::Builder()
+                     .SetId(kDisplayIds[0])
+                     .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
+                     .SetNativeMode(MakeDisplayMode(1920, 1600, false, 60.0))
+                     .AddMode(MakeDisplayMode(1920, 1600, false, 60.0))
+                     .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))
+                     .AddMode(MakeDisplayMode(1920, 1080, true, 60.0))
+                     .AddMode(MakeDisplayMode(1440, 900, true, 60.0))
+                     .Build();
+  outputs_[0] = output0.get();
+  auto output1 =
       FakeDisplaySnapshot::Builder()
           .SetId(kDisplayIds[1])
           .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
@@ -2466,7 +2492,8 @@
           .AddMode(MakeDisplayMode(1440, 900, true, 60.0))    // same AR
           .AddMode(MakeDisplayMode(500, 500, false, 60.0))
           .Build();
-  outputs_[2] =
+  outputs_[1] = output1.get();
+  auto output2 =
       FakeDisplaySnapshot::Builder()
           .SetId(kDisplayIds[2])
           .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
@@ -2476,12 +2503,13 @@
           .AddMode(MakeDisplayMode(1680, 1050, false, 60.0))  // same AR
           .AddMode(MakeDisplayMode(1440, 900, true, 60.0))    // same AR
           .Build();
+  outputs_[2] = output2.get();
 
   // Find an exactly matching mirror mode while preserving aspect.
   TestHardwareMirrorModeExist(MakeDisplayMode(1440, 900, true, 60.0));
 
   // Find an exactly matching mirror mode while not preserving aspect.
-  outputs_[2] =
+  auto output3 =
       FakeDisplaySnapshot::Builder()
           .SetId(kDisplayIds[2])
           .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
@@ -2489,10 +2517,11 @@
           .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))  // same AR
           .AddMode(MakeDisplayMode(1920, 1080, true, 60.0))
           .Build();
+  outputs_[2] = output3.get();
   TestHardwareMirrorModeExist(MakeDisplayMode(1920, 1080, true, 60.0));
 
   // Cannot find a matching mirror mode, so enable software mirroring.
-  outputs_[2] =
+  auto output4 =
       FakeDisplaySnapshot::Builder()
           .SetId(kDisplayIds[2])
           .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
@@ -2500,13 +2529,14 @@
           .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))  // same AR
           .AddMode(MakeDisplayMode(500, 500, true, 60.0))
           .Build();
+  outputs_[2] = output4.get();
   TestHardwareMirrorModeNotExist();
 }
 
 TEST_F(DisplayConfiguratorMultiMirroringTest,
        FindMirrorModeWithoutInternalDisplay) {
   // Initialize with 3 external displays.
-  outputs_[0] =
+  auto output0 =
       FakeDisplaySnapshot::Builder()
           .SetId(kDisplayIds[0])
           .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
@@ -2515,7 +2545,8 @@
           .AddMode(MakeDisplayMode(1920, 1080, false, 60.0))
           .AddMode(MakeDisplayMode(1680, 1050, true, 60.0))  // same AR
           .Build();
-  outputs_[1] =
+  outputs_[0] = output0.get();
+  auto output1 =
       FakeDisplaySnapshot::Builder()
           .SetId(kDisplayIds[1])
           .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
@@ -2524,7 +2555,8 @@
           .AddMode(MakeDisplayMode(1920, 1080, false, 60.0))
           .AddMode(MakeDisplayMode(1680, 1050, true, 60.0))  // same AR
           .Build();
-  outputs_[2] =
+  outputs_[1] = output1.get();
+  auto output2 =
       FakeDisplaySnapshot::Builder()
           .SetId(kDisplayIds[2])
           .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
@@ -2533,12 +2565,13 @@
           .AddMode(MakeDisplayMode(1920, 1080, false, 60.0))
           .AddMode(MakeDisplayMode(1680, 1050, true, 60.0))  // same AR
           .Build();
+  outputs_[2] = output2.get();
 
   // Find an exactly matching mirror mode while preserving aspect.
   TestHardwareMirrorModeExist(MakeDisplayMode(1680, 1050, true, 60.0));
 
   // Find an exactly matching mirror mode while not preserving aspect.
-  outputs_[2] =
+  auto output3 =
       FakeDisplaySnapshot::Builder()
           .SetId(kDisplayIds[2])
           .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
@@ -2547,10 +2580,11 @@
           .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))
           .AddMode(MakeDisplayMode(1920, 1080, false, 60.0))
           .Build();
+  outputs_[2] = output3.get();
   TestHardwareMirrorModeExist(MakeDisplayMode(1920, 1080, false, 60.0));
 
   // Cannot find a matching mirror mode, so enable software mirroring.
-  outputs_[2] =
+  auto output4 =
       FakeDisplaySnapshot::Builder()
           .SetId(kDisplayIds[2])
           .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
@@ -2558,6 +2592,7 @@
           .AddMode(MakeDisplayMode(1920, 1600, false, 60.0))  // same AR
           .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))
           .Build();
+  outputs_[2] = output4.get();
   TestHardwareMirrorModeNotExist();
 }
 
diff --git a/ui/display/manager/display_manager.cc b/ui/display/manager/display_manager.cc
index edebbd03..6bee063 100644
--- a/ui/display/manager/display_manager.cc
+++ b/ui/display/manager/display_manager.cc
@@ -344,16 +344,53 @@
     DisplayManager* display_manager)
     : display_manager_(display_manager) {
   if (display_manager_->notify_depth_++ == 0) {
+    CHECK(!display_manager_->pending_display_changes_.has_value());
+    display_manager_->pending_display_changes_.emplace();
     display_manager_->NotifyWillProcessDisplayChanges();
   }
 }
 
 DisplayManager::BeginEndNotifier::~BeginEndNotifier() {
   if (--display_manager_->notify_depth_ == 0) {
-    display_manager_->NotifyDidProcessDisplayChanges();
+    CHECK(display_manager_->pending_display_changes_.has_value());
+    DisplayManagerObserver::DisplayConfigurationChange config_change =
+        CreateConfigChange();
+    display_manager_->pending_display_changes_.reset();
+    display_manager_->NotifyDidProcessDisplayChanges(config_change);
   }
 }
 
+DisplayManagerObserver::DisplayConfigurationChange
+DisplayManager::BeginEndNotifier::CreateConfigChange() const {
+  CHECK(display_manager_->pending_display_changes_.has_value());
+  PendingDisplayChanges& pending_changes =
+      display_manager_->pending_display_changes_.value();
+
+  Displays added_displays;
+  for (int64_t display_id : pending_changes.added_display_ids) {
+    CHECK(display_manager_->IsDisplayIdValid(display_id));
+    added_displays.emplace_back(display_manager_->GetDisplayForId(display_id));
+  }
+
+  std::vector<DisplayManagerObserver::DisplayMetricsChange>
+      display_metrics_changes;
+  for (const auto& pair : pending_changes.display_metrics_changes) {
+    if (display_manager_->IsDisplayIdValid(pair.first)) {
+      display_metrics_changes.emplace_back(
+          DisplayManagerObserver::DisplayMetricsChange(
+              display_manager_->GetDisplayForId(pair.first), pair.second));
+    }
+  }
+
+  return {std::move(added_displays),
+          std::move(pending_changes.removed_displays),
+          std::move(display_metrics_changes)};
+}
+
+DisplayManager::PendingDisplayChanges::PendingDisplayChanges() = default;
+
+DisplayManager::PendingDisplayChanges::~PendingDisplayChanges() = default;
+
 DisplayManager::DisplayManager(std::unique_ptr<Screen> screen)
     : screen_(std::move(screen)), layout_store_(new DisplayLayoutStore) {
   SetConfigureDisplays(base::SysInfo::IsRunningOnChromeOS());
@@ -467,6 +504,8 @@
   if (GetNumDisplays() == 1) {
     return;
   }
+  // TODO(tluk): Move instantiating this to after checking whether the current
+  // layout has the same placement list.
   BeginEndNotifier notifier(this);
 
   const DisplayIdList list = GetConnectedDisplayIdList();
@@ -494,6 +533,10 @@
     NotifyMetricsChanged(GetDisplayForId(id),
                          DisplayObserver::DISPLAY_METRIC_BOUNDS |
                              DisplayObserver::DISPLAY_METRIC_WORK_AREA);
+    CHECK(pending_display_changes_.has_value());
+    pending_display_changes_->display_metrics_changes[id] |=
+        DisplayObserver::DISPLAY_METRIC_BOUNDS |
+        DisplayObserver::DISPLAY_METRIC_WORK_AREA;
   }
 
   if (delegate_) {
@@ -536,6 +579,10 @@
   bool workarea_changed = old_work_area != display->work_area();
   if (workarea_changed) {
     NotifyMetricsChanged(*display, DisplayObserver::DISPLAY_METRIC_WORK_AREA);
+
+    CHECK(pending_display_changes_.has_value());
+    pending_display_changes_->display_metrics_changes[display_id] |=
+        DisplayObserver::DISPLAY_METRIC_WORK_AREA;
   }
   return workarea_changed;
 }
@@ -825,7 +872,6 @@
       //   disconnected.
       // The display will be updated when one of displays is turned on, and the
       // display list will be updated correctly.
-
       BeginEndNotifier notifier(this);
       for (auto& display : active_display_list_) {
         if (display.detected()) {
@@ -835,6 +881,9 @@
           InsertAndUpdateDisplayInfo(info);
           NotifyMetricsChanged(display,
                                DisplayObserver::DISPLAY_METRIC_DETECTED);
+          CHECK(pending_display_changes_.has_value());
+          pending_display_changes_->display_metrics_changes[display.id()] |=
+              DisplayObserver::DISPLAY_METRIC_DETECTED;
         }
       }
     }
@@ -1195,25 +1244,22 @@
   }
 
   UpdatePrimaryDisplayIdIfNecessary();
+  const Display& primary = screen_->GetPrimaryDisplay();
+  bool notify_primary_change = delegate_ && old_primary.id() != primary.id();
 
-  bool notify_primary_change =
-      delegate_ ? old_primary.id() != screen_->GetPrimaryDisplay().id() : false;
+  for (auto& change : display_changes) {
+    Display& updated_display = active_display_list_[change.first];
+    uint32_t& updated_display_metrics = change.second;
 
-  for (auto iter = display_changes.begin(); iter != display_changes.end();
-       ++iter) {
-    uint32_t metrics = iter->second;
-    Display& updated_display = active_display_list_[iter->first];
-
-    if (notify_primary_change &&
-        updated_display.id() == screen_->GetPrimaryDisplay().id()) {
-      metrics |= DisplayObserver::DISPLAY_METRIC_PRIMARY;
+    if (notify_primary_change && updated_display.id() == primary.id()) {
+      updated_display_metrics |= DisplayObserver::DISPLAY_METRIC_PRIMARY;
       notify_primary_change = false;
     }
     if (!updated_display.detected()) {
       updated_display.set_detected(true);
-      metrics |= DisplayObserver::DISPLAY_METRIC_DETECTED;
+      updated_display_metrics |= DisplayObserver::DISPLAY_METRIC_DETECTED;
     }
-    NotifyMetricsChanged(updated_display, metrics);
+    NotifyMetricsChanged(updated_display, updated_display_metrics);
   }
 
   uint32_t primary_metrics = 0;
@@ -1221,7 +1267,6 @@
   if (notify_primary_change) {
     // This happens when a primary display has moved to anther display without
     // bounds change.
-    const Display& primary = screen_->GetPrimaryDisplay();
     if (primary.id() != old_primary.id()) {
       primary_metrics = DisplayObserver::DISPLAY_METRIC_PRIMARY;
       if (primary.size() != old_primary.size()) {
@@ -1242,6 +1287,13 @@
 
   if (delegate_ && primary_metrics) {
     NotifyMetricsChanged(screen_->GetPrimaryDisplay(), primary_metrics);
+
+    const auto primary_index_it = std::find(
+        active_display_list_.begin(), active_display_list_.end(), primary);
+    CHECK(primary_index_it != active_display_list_.end());
+    const size_t primary_index =
+        std::distance(active_display_list_.begin(), primary_index_it);
+    display_changes[primary_index] |= primary_metrics;
   }
 
   UpdateInfoForRestoringMirrorMode();
@@ -1250,6 +1302,24 @@
     delegate_->PostDisplayConfigurationChange();
   }
 
+  // Populate the pending change structure.
+  {
+    CHECK(pending_display_changes_.has_value());
+    // Currently removed displays should only be populated in
+    // `UpdateDisplaysWith()`.
+    CHECK(pending_display_changes_->removed_displays.empty());
+    pending_display_changes_->removed_displays = std::move(removed_displays);
+    base::ranges::transform(
+        added_display_indices,
+        std::back_inserter(pending_display_changes_->added_display_ids),
+        [this](size_t index) { return active_display_list_[index].id(); });
+    for (const auto& pair : display_changes) {
+      int64_t display_id = active_display_list_[pair.first].id();
+      pending_display_changes_->display_metrics_changes[display_id] |=
+          pair.second;
+    }
+  }
+
   if (mirror_mode) {
     UMA_HISTOGRAM_ENUMERATION(kMirroringImplementationHistogram,
                               IsInSoftwareMirrorMode()
@@ -1774,6 +1844,9 @@
   display->SetSize(display_info_[display_id].size_in_pixel());
   BeginEndNotifier notifier(this);
   NotifyMetricsChanged(*display, DisplayObserver::DISPLAY_METRIC_BOUNDS);
+  CHECK(pending_display_changes_.has_value());
+  pending_display_changes_->display_metrics_changes[display->id()] |=
+      DisplayObserver::DISPLAY_METRIC_BOUNDS;
   return true;
 }
 
@@ -2373,9 +2446,10 @@
   }
 }
 
-void DisplayManager::NotifyDidProcessDisplayChanges() {
+void DisplayManager::NotifyDidProcessDisplayChanges(
+    const DisplayManagerObserver::DisplayConfigurationChange& config_change) {
   for (auto& manager_observer : manager_observers_) {
-    manager_observer.OnDidProcessDisplayChanges();
+    manager_observer.OnDidProcessDisplayChanges(config_change);
   }
 }
 
diff --git a/ui/display/manager/display_manager.h b/ui/display/manager/display_manager.h
index 349254f..bb8e126 100644
--- a/ui/display/manager/display_manager.h
+++ b/ui/display/manager/display_manager.h
@@ -30,6 +30,7 @@
 #include "ui/display/display_observer.h"
 #include "ui/display/manager/display_configurator.h"
 #include "ui/display/manager/display_manager_export.h"
+#include "ui/display/manager/display_manager_observer.h"
 #include "ui/display/manager/managed_display_info.h"
 #include "ui/display/manager/touch_device_manager.h"
 #include "ui/display/manager/util/display_manager_util.h"
@@ -46,7 +47,6 @@
 
 class DisplayChangeObserver;
 class DisplayLayoutStore;
-class DisplayManagerObserver;
 class DisplayObserver;
 class NativeDisplayDelegate;
 class Screen;
@@ -498,7 +498,8 @@
   void NotifyDisplayAdded(const Display& display);
   void NotifyDisplayRemoved(const Display& display);
   void NotifyWillProcessDisplayChanges();
-  void NotifyDidProcessDisplayChanges();
+  void NotifyDidProcessDisplayChanges(
+      const DisplayManagerObserver::DisplayConfigurationChange& config_change);
 
   // Delegated from the Screen implementation.
   void AddObserver(DisplayObserver* observer);
@@ -525,9 +526,34 @@
     ~BeginEndNotifier();
 
    private:
+    // Uses the pending display change data in display manager to create the
+    // config change object propagated to observers.
+    DisplayManagerObserver::DisplayConfigurationChange CreateConfigChange()
+        const;
+
     raw_ptr<DisplayManager, ExperimentalAsh> display_manager_;
   };
 
+  // Tracks the in-progress change to the current display configuration. This is
+  // reported to observers when the last BeginEndNotifier goes out of scope.
+  struct PendingDisplayChanges {
+    PendingDisplayChanges();
+    PendingDisplayChanges(const PendingDisplayChanges&) = delete;
+    PendingDisplayChanges& operator=(const PendingDisplayChanges&) = delete;
+    ~PendingDisplayChanges();
+
+    // Store added display_ids to avoid copying potentially stale display
+    // objects while update state is accumulated.
+    DisplayIdList added_display_ids;
+
+    // Store displays by value as removed displays are no longer persisted by
+    // the manager once removed from the `active_display_list_`.
+    Displays removed_displays;
+
+    // Maps the display_id to its metrics change.
+    base::flat_map<int64_t, uint32_t> display_metrics_changes;
+  };
+
   void set_change_display_upon_host_resize(bool value) {
     change_display_upon_host_resize_ = value;
   }
@@ -716,6 +742,11 @@
   // OnWillProcessDisplayChanges() and OnDidProcessDisplayChanges().
   int notify_depth_ = 0;
 
+  // State accumulated during a display configuration update. Created when
+  // BeginEndNotifier is created and propagated in OnDidProcessDisplayChanges()
+  // when the last BeginEndNotifier is destroyed.
+  absl::optional<PendingDisplayChanges> pending_display_changes_;
+
   std::unique_ptr<display::DisplayConfigurator> display_configurator_;
 
   std::unique_ptr<TouchDeviceManager> touch_device_manager_;
diff --git a/ui/display/manager/display_manager_observer.cc b/ui/display/manager/display_manager_observer.cc
new file mode 100644
index 0000000..a00e546
--- /dev/null
+++ b/ui/display/manager/display_manager_observer.cc
@@ -0,0 +1,25 @@
+// 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 "ui/display/manager/display_manager_observer.h"
+
+namespace display {
+
+DisplayManagerObserver::DisplayMetricsChange::DisplayMetricsChange(
+    const Display& display,
+    uint32_t changed_metrics)
+    : display(display), changed_metrics(changed_metrics) {}
+
+DisplayManagerObserver::DisplayConfigurationChange::DisplayConfigurationChange(
+    Displays added_displays,
+    Displays removed_displays,
+    std::vector<DisplayMetricsChange> display_metrics_changes)
+    : added_displays(added_displays),
+      removed_displays(removed_displays),
+      display_metrics_changes(display_metrics_changes) {}
+
+DisplayManagerObserver::DisplayConfigurationChange::
+    ~DisplayConfigurationChange() = default;
+
+}  // namespace display
diff --git a/ui/display/manager/display_manager_observer.h b/ui/display/manager/display_manager_observer.h
index 82724da..983f696 100644
--- a/ui/display/manager/display_manager_observer.h
+++ b/ui/display/manager/display_manager_observer.h
@@ -5,7 +5,11 @@
 #ifndef UI_DISPLAY_MANAGER_DISPLAY_MANAGER_OBSERVER_H_
 #define UI_DISPLAY_MANAGER_DISPLAY_MANAGER_OBSERVER_H_
 
+#include <vector>
+
+#include "base/memory/raw_ref.h"
 #include "base/observer_list_types.h"
+#include "ui/display/display.h"
 #include "ui/display/manager/display_manager_export.h"
 
 namespace display {
@@ -13,13 +17,37 @@
 class DISPLAY_MANAGER_EXPORT DisplayManagerObserver
     : public base::CheckedObserver {
  public:
+  using Displays = std::vector<Display>;
+
+  // Wraps updated metrics for `display`. `changed_metrics` is a bitmask of
+  // DisplayObserver::DisplayMetric types.
+  struct DISPLAY_EXPORT DisplayMetricsChange {
+    DisplayMetricsChange(const Display& display, uint32_t changed_metrics);
+    const raw_ref<const Display> display;
+    const uint32_t changed_metrics;
+  };
+
+  // Represents an atomic change in the display configuration of the user's
+  // desktop environment maintained by DisplayManager.
+  struct DISPLAY_EXPORT DisplayConfigurationChange {
+    DisplayConfigurationChange(
+        Displays added_displays,
+        Displays removed_displays,
+        std::vector<DisplayMetricsChange> display_metrics_changes);
+    ~DisplayConfigurationChange();
+    const Displays added_displays;
+    const Displays removed_displays;
+    const std::vector<DisplayMetricsChange> display_metrics_changes;
+  };
+
   // Called before the DisplayManager begins processing a change / update to
   // the current display configuration.
   virtual void OnWillProcessDisplayChanges() {}
 
   // Called after the display configuration changes processed by the
   // DisplayManager have completed.
-  virtual void OnDidProcessDisplayChanges() {}
+  virtual void OnDidProcessDisplayChanges(
+      const DisplayConfigurationChange& configuration_change) {}
 };
 
 }  // namespace display
diff --git a/ui/views/controls/menu/menu_runner.cc b/ui/views/controls/menu/menu_runner.cc
index 76c758b4..acd06121 100644
--- a/ui/views/controls/menu/menu_runner.cc
+++ b/ui/views/controls/menu/menu_runner.cc
@@ -4,9 +4,12 @@
 
 #include "ui/views/controls/menu/menu_runner.h"
 
+#include <memory>
 #include <utility>
 
+#include "base/memory/ptr_util.h"
 #include "ui/gfx/geometry/rounded_corners_f.h"
+#include "ui/views/controls/menu/menu_item_view.h"
 #include "ui/views/controls/menu/menu_runner_handler.h"
 #include "ui/views/controls/menu/menu_runner_impl.h"
 #include "ui/views/views_delegate.h"
@@ -23,8 +26,12 @@
           run_types,
           std::move(on_menu_closed_callback))) {}
 
-MenuRunner::MenuRunner(MenuItemView* menu_view, int32_t run_types)
-    : run_types_(run_types), impl_(new internal::MenuRunnerImpl(menu_view)) {}
+MenuRunner::MenuRunner(std::unique_ptr<MenuItemView> menu, int32_t run_types)
+    : run_types_(run_types),
+      impl_(new internal::MenuRunnerImpl(std::move(menu))) {}
+
+MenuRunner::MenuRunner(MenuItemView* menu, int32_t run_types)
+    : MenuRunner(base::WrapUnique<MenuItemView>(menu), run_types) {}
 
 MenuRunner::~MenuRunner() {
   // Release causes the deletion of the object.
diff --git a/ui/views/controls/menu/menu_runner.h b/ui/views/controls/menu/menu_runner.h
index 8f47921f..29a7088 100644
--- a/ui/views/controls/menu/menu_runner.h
+++ b/ui/views/controls/menu/menu_runner.h
@@ -127,6 +127,9 @@
                  base::RepeatingClosure());
 
   // Creates a runner for a custom-created toolkit-views menu.
+  MenuRunner(std::unique_ptr<MenuItemView> menu, int32_t run_types);
+  // Deprecated: Move to the above version that makes it explicit that
+  // `MenuRunner` takes ownership of `MenuItemView`.
   MenuRunner(MenuItemView* menu, int32_t run_types);
 
   MenuRunner(const MenuRunner&) = delete;
diff --git a/ui/views/controls/menu/menu_runner_impl.cc b/ui/views/controls/menu/menu_runner_impl.cc
index a21e0d7..8100d5e 100644
--- a/ui/views/controls/menu/menu_runner_impl.cc
+++ b/ui/views/controls/menu/menu_runner_impl.cc
@@ -71,10 +71,8 @@
 }
 #endif
 
-MenuRunnerImpl::MenuRunnerImpl(MenuItemView* menu)
-    : menu_(menu),
-
-      controller_(nullptr) {}
+MenuRunnerImpl::MenuRunnerImpl(std::unique_ptr<MenuItemView> menu)
+    : menu_(std::move(menu)) {}
 
 bool MenuRunnerImpl::IsRunning() const {
   return running_;
@@ -188,7 +186,7 @@
         std::move(show_menu_host_duration_histogram));
   }
 
-  controller->Run(parent, button_controller, menu_, bounds, anchor,
+  controller->Run(parent, button_controller, menu_.get(), bounds, anchor,
                   (run_types & MenuRunner::CONTEXT_MENU) != 0,
                   (run_types & MenuRunner::NESTED_DRAG) != 0,
                   native_view_for_gestures);
@@ -247,12 +245,12 @@
 }
 
 void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) {
-  if (menu != menu_ && sibling_menus_.count(menu) == 0)
+  if (menu != menu_.get() && sibling_menus_.count(menu) == 0) {
     sibling_menus_.insert(menu);
+  }
 }
 
 MenuRunnerImpl::~MenuRunnerImpl() {
-  delete menu_;
   for (auto* sibling_menu : sibling_menus_)
     delete sibling_menu;
 }
diff --git a/ui/views/controls/menu/menu_runner_impl.h b/ui/views/controls/menu/menu_runner_impl.h
index e0faa131..9b2ac3f 100644
--- a/ui/views/controls/menu/menu_runner_impl.h
+++ b/ui/views/controls/menu/menu_runner_impl.h
@@ -40,7 +40,7 @@
 class VIEWS_EXPORT MenuRunnerImpl : public MenuRunnerImplInterface,
                                     public MenuControllerDelegate {
  public:
-  explicit MenuRunnerImpl(MenuItemView* menu);
+  explicit MenuRunnerImpl(std::unique_ptr<MenuItemView> menu);
 
   MenuRunnerImpl(const MenuRunnerImpl&) = delete;
   MenuRunnerImpl& operator=(const MenuRunnerImpl&) = delete;
@@ -73,9 +73,8 @@
   // Returns true if mnemonics should be shown in the menu.
   bool ShouldShowMnemonics(int32_t run_types);
 
-  // The menu. We own this. We don't use scoped_ptr as the destructor is
-  // protected and we're a friend.
-  raw_ptr<MenuItemView, DanglingUntriaged> menu_;
+  // The menu.
+  std::unique_ptr<MenuItemView> menu_;
 
   // Any sibling menus. Does not include |menu_|. We own these too.
   std::set<MenuItemView*> sibling_menus_;
diff --git a/ui/views/controls/menu/menu_runner_impl_adapter.cc b/ui/views/controls/menu/menu_runner_impl_adapter.cc
index 02fab92a..b56ec1d25 100644
--- a/ui/views/controls/menu/menu_runner_impl_adapter.cc
+++ b/ui/views/controls/menu/menu_runner_impl_adapter.cc
@@ -6,6 +6,8 @@
 
 #include <utility>
 
+#include "base/memory/ptr_util.h"
+#include "ui/views/controls/menu/menu_item_view.h"
 #include "ui/views/controls/menu/menu_model_adapter.h"
 #include "ui/views/controls/menu/menu_runner_impl.h"
 
@@ -16,7 +18,8 @@
     base::RepeatingClosure on_menu_done_callback)
     : menu_model_adapter_(
           new MenuModelAdapter(menu_model, std::move(on_menu_done_callback))),
-      impl_(new MenuRunnerImpl(menu_model_adapter_->CreateMenu())) {}
+      impl_(new MenuRunnerImpl(
+          base::WrapUnique<MenuItemView>(menu_model_adapter_->CreateMenu()))) {}
 
 bool MenuRunnerImplAdapter::IsRunning() const {
   return impl_->IsRunning();
diff --git a/ui/views/controls/menu/menu_runner_unittest.cc b/ui/views/controls/menu/menu_runner_unittest.cc
index eb8996d..3ee96e8 100644
--- a/ui/views/controls/menu/menu_runner_unittest.cc
+++ b/ui/views/controls/menu/menu_runner_unittest.cc
@@ -52,26 +52,23 @@
 
   ~MenuRunnerTest() override = default;
 
-  // Initializes the delegates and views needed for a menu. It does not create
-  // the MenuRunner.
-  void InitMenuViews() {
-    menu_delegate_ = std::make_unique<TestMenuDelegate>();
-    menu_item_view_ = new views::TestMenuItemView(menu_delegate_.get());
-    menu_item_view_->AppendMenuItem(1, u"One");
-    menu_item_view_->AppendMenuItem(2, u"\x062f\x0648");
-
-    owner_ = std::make_unique<Widget>();
-    Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
-    params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
-    owner_->Init(std::move(params));
-    owner_->Show();
+  // Creates a `TestMenuItemView` and retains a raw pointer to it. Call
+  // `ResetMenuItemView` if you destroy it prior to test tear-down.
+  std::unique_ptr<MenuItemView> CreateMenuItemView() {
+    auto menu_item_view =
+        std::make_unique<TestMenuItemView>(menu_delegate_.get());
+    menu_item_view->AppendMenuItem(1, u"One");
+    menu_item_view->AppendMenuItem(2, u"\x062f\x0648");
+    menu_item_view_ = menu_item_view.get();
+    return menu_item_view;
   }
 
-  // Initializes all delegates and views needed for a menu. A MenuRunner is also
-  // created with |run_types|, it takes ownership of |menu_item_view_|.
+  void ResetMenuItemView() { menu_item_view_ = nullptr; }
+
+  // Creates a menuRunner with `run_types`.
   void InitMenuRunner(int32_t run_types) {
-    InitMenuViews();
-    menu_runner_ = std::make_unique<MenuRunner>(menu_item_view_, run_types);
+    menu_runner_ =
+        std::make_unique<MenuRunner>(CreateMenuItemView(), run_types);
   }
 
   views::TestMenuItemView* menu_item_view() { return menu_item_view_; }
@@ -79,18 +76,23 @@
   MenuRunner* menu_runner() { return menu_runner_.get(); }
   Widget* owner() { return owner_.get(); }
 
-#if BUILDFLAG(IS_MAC)
   void SetUp() override {
     ViewsTestBase::SetUp();
 
+#if BUILDFLAG(IS_MAC)
     // Ignore app activation notifications during tests (they make the tests
     // flaky).
     MenuCocoaWatcherMac::SetNotificationFilterForTesting(
         MacNotificationFilter::IgnoreWorkspaceNotifications);
-  }
 #endif
 
-  void ResetMenuItemView() { menu_item_view_ = nullptr; }
+    menu_delegate_ = std::make_unique<TestMenuDelegate>();
+    Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
+    params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+    owner_ = std::make_unique<Widget>();
+    owner_->Init(std::move(params));
+    owner_->Show();
+  }
 
   // ViewsTestBase:
   void TearDown() override {
@@ -500,21 +502,14 @@
   MenuRunnerImplTest& operator=(const MenuRunnerImplTest&) = delete;
 
   ~MenuRunnerImplTest() override = default;
-
-  void SetUp() override;
 };
 
-void MenuRunnerImplTest::SetUp() {
-  MenuRunnerTest::SetUp();
-  InitMenuViews();
-}
-
 // Tests that when nested menu runners are destroyed out of order, that
 // MenuController is not accessed after it has been destroyed. This should not
 // crash on ASAN bots.
 TEST_F(MenuRunnerImplTest, NestedMenuRunnersDestroyedOutOfOrder) {
   internal::MenuRunnerImpl* menu_runner =
-      new internal::MenuRunnerImpl(menu_item_view());
+      new internal::MenuRunnerImpl(CreateMenuItemView());
   menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
                          MenuAnchorPosition::kTopLeft, 0, nullptr);
 
@@ -522,8 +517,8 @@
   MenuItemView* menu_item_view2 = new MenuItemView(menu_delegate2.get());
   menu_item_view2->AppendMenuItem(1, u"One");
 
-  internal::MenuRunnerImpl* menu_runner2 =
-      new internal::MenuRunnerImpl(menu_item_view2);
+  internal::MenuRunnerImpl* menu_runner2 = new internal::MenuRunnerImpl(
+      base::WrapUnique<MenuItemView>(menu_item_view2));
   menu_runner2->RunMenuAt(owner(), nullptr, gfx::Rect(),
                           MenuAnchorPosition::kTopLeft, MenuRunner::IS_NESTED,
                           nullptr);
@@ -548,7 +543,7 @@
 // bots.
 TEST_F(MenuRunnerImplTest, MenuRunnerDestroyedWithNoActiveController) {
   internal::MenuRunnerImpl* menu_runner =
-      new internal::MenuRunnerImpl(menu_item_view());
+      new internal::MenuRunnerImpl(CreateMenuItemView());
   menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
                          MenuAnchorPosition::kTopLeft, 0, nullptr);
 
@@ -561,8 +556,8 @@
   MenuItemView* menu_item_view2 = new MenuItemView(menu_delegate2.get());
   menu_item_view2->AppendMenuItem(1, u"One");
 
-  internal::MenuRunnerImpl* menu_runner2 =
-      new internal::MenuRunnerImpl(menu_item_view2);
+  internal::MenuRunnerImpl* menu_runner2 = new internal::MenuRunnerImpl(
+      base::WrapUnique<MenuItemView>(menu_item_view2));
   menu_runner2->RunMenuAt(owner(), nullptr, gfx::Rect(),
                           MenuAnchorPosition::kTopLeft, MenuRunner::FOR_DROP,
                           nullptr);
@@ -612,14 +607,13 @@
 void MenuRunnerDestructionTest::SetUp() {
   set_views_delegate(std::make_unique<ReleaseRefTestViewsDelegate>());
   MenuRunnerTest::SetUp();
-  InitMenuViews();
 }
 
 // Tests that when ViewsDelegate is released that a nested Cancel of the
 // MenuRunner does not occur.
 TEST_F(MenuRunnerDestructionTest, MenuRunnerDestroyedDuringReleaseRef) {
   internal::MenuRunnerImpl* menu_runner =
-      new internal::MenuRunnerImpl(menu_item_view());
+      new internal::MenuRunnerImpl(CreateMenuItemView());
   menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
                          MenuAnchorPosition::kTopLeft, 0, nullptr);
 
@@ -644,7 +638,7 @@
 
 TEST_F(MenuRunnerImplTest, FocusOnMenuClose) {
   internal::MenuRunnerImpl* menu_runner =
-      new internal::MenuRunnerImpl(menu_item_view());
+      new internal::MenuRunnerImpl(CreateMenuItemView());
 
   // Create test button that has focus.
   auto button_managed = std::make_unique<LabelButton>();
@@ -705,7 +699,7 @@
   button->RequestFocus();
 
   internal::MenuRunnerImpl* menu_runner =
-      new internal::MenuRunnerImpl(menu_item_view());
+      new internal::MenuRunnerImpl(CreateMenuItemView());
   menu_runner->RunMenuAt(owner(), nullptr, gfx::Rect(),
                          MenuAnchorPosition::kTopLeft, 0, nullptr);
 
@@ -718,8 +712,8 @@
   MenuItemView* menu_item_view2 = new MenuItemView(menu_delegate2.get());
   menu_item_view2->AppendMenuItem(1, u"One");
 
-  internal::MenuRunnerImpl* menu_runner2 =
-      new internal::MenuRunnerImpl(menu_item_view2);
+  internal::MenuRunnerImpl* menu_runner2 = new internal::MenuRunnerImpl(
+      base::WrapUnique<MenuItemView>(menu_item_view2));
   menu_runner2->RunMenuAt(owner(), nullptr, gfx::Rect(),
                           MenuAnchorPosition::kTopLeft, MenuRunner::FOR_DROP,
                           nullptr);
diff --git a/ui/views/controls/native/native_view_host_aura_unittest.cc b/ui/views/controls/native/native_view_host_aura_unittest.cc
index a49416e1..df173fa 100644
--- a/ui/views/controls/native/native_view_host_aura_unittest.cc
+++ b/ui/views/controls/native/native_view_host_aura_unittest.cc
@@ -43,11 +43,24 @@
   };
 
   struct EventDetails {
+    static int id;
+
+    EventDetails(EventType event_type,
+                 aura::Window& window,
+                 const gfx::Rect& event_bounds)
+        : type(event_type), bounds(event_bounds) {
+      if (window.GetId() == aura::Window::kInitialId) {
+        window.SetId(++id);
+      }
+      window_id = window.GetId();
+    }
+
     EventType type;
-    raw_ptr<aura::Window, DanglingUntriaged> window;
+    int window_id;
     gfx::Rect bounds;
     bool operator!=(const EventDetails& rhs) {
-      return type != rhs.type || window != rhs.window || bounds != rhs.bounds;
+      return type != rhs.type || window_id != rhs.window_id ||
+             bounds != rhs.bounds;
     }
   };
 
@@ -63,10 +76,8 @@
 
   // aura::WindowObserver overrides
   void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
-    EventDetails event;
-    event.type = visible ? EVENT_SHOWN : EVENT_HIDDEN;
-    event.window = window;
-    event.bounds = window->GetBoundsInRootWindow();
+    EventDetails event(visible ? EVENT_SHOWN : EVENT_HIDDEN, *window,
+                       window->GetBoundsInRootWindow());
 
     // Dedupe events as a single Hide() call can result in several
     // notifications.
@@ -78,15 +89,13 @@
                              const gfx::Rect& old_bounds,
                              const gfx::Rect& new_bounds,
                              ui::PropertyChangeReason reason) override {
-    EventDetails event;
-    event.type = EVENT_BOUNDS_CHANGED;
-    event.window = window;
-    event.bounds = window->GetBoundsInRootWindow();
+    EventDetails event(EVENT_BOUNDS_CHANGED, *window,
+                       window->GetBoundsInRootWindow());
     events_.push_back(event);
   }
 
   void OnWindowDestroyed(aura::Window* window) override {
-    EventDetails event = {EVENT_DESTROYED, window, gfx::Rect()};
+    EventDetails event(EVENT_DESTROYED, *window, gfx::Rect());
     events_.push_back(event);
   }
 
@@ -95,6 +104,8 @@
   gfx::Rect bounds_at_visibility_changed_;
 };
 
+int NativeViewHostWindowObserver::EventDetails::id = 1;
+
 class NativeViewHostAuraTest : public test::NativeViewHostTestBase {
  public:
   NativeViewHostAuraTest() = default;
@@ -380,7 +391,7 @@
   ASSERT_GE(test_observer.events().size(), 1u);
   EXPECT_EQ(NativeViewHostWindowObserver::EVENT_HIDDEN,
             test_observer.events()[0].type);
-  EXPECT_EQ(clipping_window(), test_observer.events()[0].window);
+  EXPECT_EQ(clipping_window()->GetId(), test_observer.events()[0].window_id);
 
   clipping_window()->RemoveObserver(&test_observer);
   child()->GetNativeView()->RemoveObserver(&test_observer);
@@ -414,17 +425,19 @@
   ASSERT_EQ(3u, test_observer.events().size());
   EXPECT_EQ(NativeViewHostWindowObserver::EVENT_BOUNDS_CHANGED,
             test_observer.events()[0].type);
-  EXPECT_EQ(child()->GetNativeView(), test_observer.events()[0].window);
+  EXPECT_EQ(child()->GetNativeView()->GetId(),
+            test_observer.events()[0].window_id);
   EXPECT_EQ(expected_bounds.ToString(),
             test_observer.events()[0].bounds.ToString());
   EXPECT_EQ(NativeViewHostWindowObserver::EVENT_SHOWN,
             test_observer.events()[1].type);
-  EXPECT_EQ(child()->GetNativeView(), test_observer.events()[1].window);
+  EXPECT_EQ(child()->GetNativeView()->GetId(),
+            test_observer.events()[1].window_id);
   EXPECT_EQ(expected_bounds.ToString(),
             test_observer.events()[1].bounds.ToString());
   EXPECT_EQ(NativeViewHostWindowObserver::EVENT_SHOWN,
             test_observer.events()[2].type);
-  EXPECT_EQ(clipping_window(), test_observer.events()[2].window);
+  EXPECT_EQ(clipping_window()->GetId(), test_observer.events()[2].window_id);
   EXPECT_EQ(expected_bounds.ToString(),
             test_observer.events()[2].bounds.ToString());
 
@@ -623,7 +636,7 @@
   }
 
  private:
-  raw_ptr<aura::Window, DanglingUntriaged> window_ = nullptr;
+  raw_ptr<aura::Window> window_ = nullptr;
 };
 
 TEST_F(NativeViewHostAuraTest, ShouldDescendIntoChildForEventHandling) {
@@ -656,6 +669,7 @@
   // Because the delegate overrides ShouldDescendIntoChildForEventHandling()
   // the NativeView does not get the event, but NativeViewHost will.
   EXPECT_EQ(1, on_mouse_pressed_called_count());
+  widget_delegate.set_window(nullptr);
   DestroyHost();
   DestroyTopLevel();
 }
diff --git a/ui/webui/resources/cr_components/app_management/app_content_dialog.html b/ui/webui/resources/cr_components/app_management/app_content_dialog.html
index d76a2b6..e6a8086d 100644
--- a/ui/webui/resources/cr_components/app_management/app_content_dialog.html
+++ b/ui/webui/resources/cr_components/app_management/app_content_dialog.html
@@ -20,7 +20,7 @@
     user-select: none;
   }
 </style>
-<cr-dialog id="dialog" show-on-attach show-close-button>
+<cr-dialog id="dialog" close-text="[[i18n('close')]]" show-on-attach show-close-button >
   <div slot="title">[[i18n('appManagementAppContentLabel')]]</div>
   <div slot="body">[[i18n('appManagementAppContentDialogSublabel')]]</div>
   <div id="dialogBody" slot="body" scrollable>