diff --git a/DEPS b/DEPS
index d5c8c1a..8bc91337 100644
--- a/DEPS
+++ b/DEPS
@@ -304,15 +304,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '284bc9fe6264205e4b08170f549ce8e1d32e3caf',
+  'src_internal_revision': '88e16d7638ad0e2c73a70bbd704a06bacd4224ae',
   # 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': '0bbcd8152d32b349e5eee99b29bccc5fcd58ce9b',
+  'skia_revision': '530fa373b797bf1ade172ef591134ea6cc401271',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'f0366cb2215c9ee342a870b52db0769aad7b20d4',
+  'v8_revision': '06b1e2c29489af4922ca60a44347ce0ef7c36ebb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -324,7 +324,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '547d96118caf46c07b3e520a971992b6909696bd',
+  'pdfium_revision': 'f98c54df162cd2a9324dad9e5b9453425311527d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -383,7 +383,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': '83915e39be4ead9df32167feafc7c2639d603d5a',
+  'chromium_variations_revision': 'c90f785907da5911ca61f1116990ad6a7fdb6f67',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -423,7 +423,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': 'd4d6d18aec677d0e43edac057c0595ed3973f4b7',
+  'dawn_revision': '724dd7855543eb1d4c75c517de7b576b092bfd7f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -447,7 +447,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling crabbyavif
   # and whatever else without interference from each other.
-  'crabbyavif_revision': 'ef17807890f60bee1398a752d53204c369076aca',
+  'crabbyavif_revision': '2a37e62739815ac00d1195e88135e8b1e5f38c87',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libavifinfo
   # and whatever else without interference from each other.
@@ -1004,7 +1004,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'fc1c0897381a14bedb2015ed8d4e0551d2f72088',
+    '96dc252dcd4740e9bbe8822a8357c5b678cddf01',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1425,7 +1425,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '5ff757e963883d28db6d5b9856e5c7fcc63cdcb0',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '8d0f67b603baecf2da3e1ad830062e356430f5ab',
       'condition': 'checkout_chromeos',
   },
 
@@ -1460,7 +1460,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '274689c4a55104b92bf66248d03249ce0175aa02',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e0038c0721935fc3d8119e02c19553f2fee8f8fd',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1930,7 +1930,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '09a4f3ec842a8932341b195c5b01e141c8a16eb7',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + '6a097c705fc4bb3d79bcb4a45f71b7a198ba40f8',
+    Var('chromium_git') + '/openscreen' + '@' + 'a74a035f778762b1c31ae1e9393eefdbf07be714',
 
   'src/third_party/openxr/src': {
     'url': Var('chromium_git') + '/external/github.com/KhronosGroup/OpenXR-SDK' + '@' + '95fe35ffb383710a6e0567e958ead9a3b66e930c',
@@ -1956,7 +1956,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '3f851caa743d632b338f79ee30e9ae2929eed22d',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'e03af0caee1bf0b7ece55e42e607d738f65f9c7b',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '8ef97ff3b7332e38e61b347a2fbed425a4617151',
@@ -2173,7 +2173,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '789c2834ebb5eb936190128379ae15ac70541b12',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'b25c747d5d318827869fe8304bd975285b33bf57',
+    Var('webrtc_git') + '/src.git' + '@' + '21922847462881016e05b008ffbf7d25c602074b',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -2296,7 +2296,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'LahA7KBiariHlcVMa5sbbjbhVExazWGFemCM8WZzAlEC',
+        'version': 'IrldqhI9fm0-uieXg7Zh2UJPJEIVkJsDlmbDOgDIibIC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -2307,7 +2307,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': '0PJ4YF24hxSLkgBddDmjHzKDfo21-MvdPIpsAg9TTZYC',
+        'version': 'ugSq-EY57Bf7CmYZg1N033tZoq1YSPg3bnl6W1XFgsgC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4214,7 +4214,7 @@
 
   'src/chrome/browser/platform_experience/win': {
       'url': Var('chrome_git') + '/chrome/browser/platform_experience/win.git' + '@' +
-        '3d27dce9761364362fb066b34eca50b767ab53d4',
+        '203bb990e01a33360e543edbf03db386355a4d26',
       'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_crashy_class_utils.cc b/android_webview/browser/aw_crashy_class_utils.cc
index 57f5e4a..7b95472 100644
--- a/android_webview/browser/aw_crashy_class_utils.cc
+++ b/android_webview/browser/aw_crashy_class_utils.cc
@@ -5,12 +5,21 @@
 #include <ostream>
 
 #include "android_webview/browser_jni_headers/AwCrashyClassUtils_jni.h"
-#include "base/check.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
 
 namespace android_webview {
 
+// Do not inline this function. This is meant to simulate a crash like we would
+// see in a real incident. Real crashes will generally have at least 1
+// non-inlined stack frame, so we force this to happen in this case using both
+// the NOINLINE and NOOPT annotations (for some reason, NOINLINE is not enough).
+NOINLINE NOOPT void ThisFunctionWillCrash() {
+  LOG(FATAL) << "WebView Forced Native Crash for WebView Browser Process";
+}
+
 static void JNI_AwCrashyClassUtils_CrashInNative(JNIEnv* env) {
-  CHECK(false) << "WebView Forced Native Crash for WebView Browser Process";
+  ThisFunctionWillCrash();
 }
 
 }  // namespace android_webview
diff --git a/android_webview/browser/aw_feature_map.cc b/android_webview/browser/aw_feature_map.cc
index e6176ac..6e852ca 100644
--- a/android_webview/browser/aw_feature_map.cc
+++ b/android_webview/browser/aw_feature_map.cc
@@ -26,7 +26,6 @@
     &features::kWebViewInvokeZoomPickerOnGSU,
     &features::kWebViewMixedContentAutoupgrades,
     &features::kWebViewTestFeature,
-    &features::kWebViewJavaJsBridgeMojo,
     &features::kWebViewUseMetricsUploadService,
     &features::kWebViewUseMetricsUploadServiceOnlySdkRuntime,
     &features::kWebViewXRequestedWithHeaderControl,
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index 8f2a351..e0f1172 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -106,11 +106,6 @@
              "WebViewExtraHeadersSameOriginOnly",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enable the new Java/JS Bridge code path with mojo implementation.
-BASE_FEATURE(kWebViewJavaJsBridgeMojo,
-             "WebViewJavaJsBridgeMojo",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Whether to record size of the embedding app's data directory to the UMA
 // histogram Android.WebView.AppDataDirectorySize.
 BASE_FEATURE(kWebViewRecordAppDataDirectorySize,
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index 4f3846c..8b565f20 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -32,7 +32,6 @@
 // Feature parameter for `network::features::kMaskedDomainList` which is
 // defined in //services/network.
 extern const base::FeatureParam<int> kWebViewIpProtectionExclusionCriteria;
-BASE_DECLARE_FEATURE(kWebViewJavaJsBridgeMojo);
 BASE_DECLARE_FEATURE(kWebViewMediaIntegrityApi);
 BASE_DECLARE_FEATURE(kWebViewMediaIntegrityApiBlinkExtension);
 BASE_DECLARE_FEATURE(kWebViewMixedContentAutoupgrades);
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index 03081b5..2166dac 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -2067,10 +2067,7 @@
 
     private JavascriptInjector getJavascriptInjector() {
         if (mJavascriptInjector == null) {
-            mJavascriptInjector =
-                    JavascriptInjector.fromWebContents(
-                            mWebContents,
-                            AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_JAVA_JS_BRIDGE_MOJO));
+            mJavascriptInjector = JavascriptInjector.fromWebContents(mWebContents);
         }
         return mJavascriptInjector;
     }
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 58964b6..1aa9e62 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
@@ -208,9 +208,6 @@
                 "Enables autoupgrades for audio/video/image mixed content when mixed content "
                         + "mode is set to MIXED_CONTENT_COMPATIBILITY_MODE"),
         Flag.baseFeature(
-                AwFeatures.WEBVIEW_JAVA_JS_BRIDGE_MOJO,
-                "Enables the new Java/JS Bridge code path with mojo implementation."),
-        Flag.baseFeature(
                 BlinkFeatures.GMS_CORE_EMOJI,
                 "Enables retrieval of the emoji font through GMS Core "
                         + "improving emoji glyph coverage."),
@@ -879,6 +876,9 @@
                 "MojoChannelAssociatedSendUsesRunOrPostTask",
                 "Enables optimization for sending messages on channel-associated interfaces"),
         Flag.baseFeature(
+                "MojoChannelAssociatedCrashesOnSendError",
+                "Enable a CHECK to verify if there are Mojo send errors in the field"),
+        Flag.baseFeature(
                 "MojoBindingsInlineSLS",
                 "Enable small value optimization for current Mojo dispatch context storage"),
         Flag.baseFeature(
@@ -929,6 +929,7 @@
                 "DoNotEvictOnAXLocationChange",
                 "When enabled, do not evict the bfcache entry even when AXLocationChange happens."),
         Flag.baseFeature("PassHistogramSharedMemoryOnLaunch"),
+        Flag.baseFeature("PumpFastToSleepAndroid"),
         Flag.baseFeature(
                 BlinkFeatures.NO_THROTTLING_VISIBLE_AGENT,
                 "Do not throttle Javascript timers to 1Hz on hidden cross-origin frames that are"
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java
index 119730b..e027df0 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java
@@ -502,7 +502,7 @@
         InstrumentationRegistry.getInstrumentation()
                 .runOnMainSync(
                         () -> {
-                            JavascriptInjector.fromWebContents(awContents.getWebContents(), false)
+                            JavascriptInjector.fromWebContents(awContents.getWebContents())
                                     .addPossiblyUnsafeInterface(
                                             pageChangeNotifier, "pageChangeNotifier", null);
                             awContents.loadUrl(WAIT_FOR_JS_DETACHED_TEST_URL);
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/util/JavascriptEventObserver.java b/android_webview/javatests/src/org/chromium/android_webview/test/util/JavascriptEventObserver.java
index 0ca18b6..f8a2c46e 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/util/JavascriptEventObserver.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/util/JavascriptEventObserver.java
@@ -4,8 +4,6 @@
 
 package org.chromium.android_webview.test.util;
 
-import org.chromium.android_webview.AwFeatureMap;
-import org.chromium.android_webview.common.AwFeatures;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.content_public.browser.JavascriptInjector;
 import org.chromium.content_public.browser.WebContents;
@@ -32,8 +30,7 @@
      * @param name the name of object used in javascript
      */
     public void register(WebContents webContents, String name) {
-        JavascriptInjector.fromWebContents(
-                        webContents, AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_JAVA_JS_BRIDGE_MOJO))
+        JavascriptInjector.fromWebContents(webContents)
                 .addPossiblyUnsafeInterface(this, name, null);
     }
 
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 f3a27ce..adb1449 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
@@ -107,4 +107,6 @@
     int getWebViewMediaIntegrityApiDefaultStatus();
 
     Map<String, @WebViewMediaIntegrityApiStatus Integer> getWebViewMediaIntegrityApiOverrideRules();
+
+    void setPreloadingEnabled(boolean preloadingEnabled);
 }
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 95b6ff57..75298f9 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
@@ -271,4 +271,7 @@
     // WebViewCompat.setAudioMuted
     // WebViewCompat.isAudioMuted
     public static final String MUTE_AUDIO = "MUTE_AUDIO";
+
+    // WebSettingsCompat.setPreloadingEnabled
+    public static final String ENABLE_PRELOADING = "ENABLE_PRELOADING";
 }
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 bddaaa3..eb18693a 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
@@ -11,6 +11,7 @@
 import org.chromium.android_webview.AwDarkMode;
 import org.chromium.android_webview.AwSettings;
 import org.chromium.android_webview.common.MediaIntegrityApiStatus;
+import org.chromium.android_webview.settings.PreloadingAllowedFlags;
 import org.chromium.base.Log;
 import org.chromium.base.TraceEvent;
 import org.chromium.components.webauthn.WebauthnMode;
@@ -433,4 +434,16 @@
         // unreached
         throw new IllegalArgumentException("Invalid WebView Media Integrity API status: " + status);
     }
+
+    @Override
+    public void setPreloadingEnabled(boolean preloadingEnabled) {
+        try (TraceEvent event =
+                TraceEvent.scoped("WebView.APICall.AndroidX.SET_PRELOADING_ENABLED")) {
+            recordApiCall(ApiCall.SET_PRELOADING_ENABLED);
+            mAwSettings.setPreloadingAllowed(
+                    preloadingEnabled
+                            ? PreloadingAllowedFlags.PRERENDER_ENABLED
+                            : PreloadingAllowedFlags.PRELOADING_DISABLED);
+        }
+    }
 }
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 7e9f322..0db6b3d 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
@@ -94,6 +94,7 @@
                 Features.WEBVIEW_MEDIA_INTEGRITY_API_STATUS,
                 Features.MUTE_AUDIO,
                 Features.WEB_AUTHENTICATION,
+                Features.ENABLE_PRELOADING + 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.
@@ -205,6 +206,7 @@
         ApiCall.IS_AUDIO_MUTED,
         ApiCall.WEB_SETTINGS_SET_WEBAUTHN_SUPPORT,
         ApiCall.WEB_SETTINGS_GET_WEBAUTHN_SUPPORT,
+        ApiCall.SET_PRELOADING_ENABLED,
         // Add new constants above. The final constant should have a trailing comma for cleaner
         // diffs.
         ApiCall.COUNT, // Added to suppress WrongConstant in #recordApiCall
@@ -315,8 +317,9 @@
         int IS_AUDIO_MUTED = 101;
         int WEB_SETTINGS_SET_WEBAUTHN_SUPPORT = 102;
         int WEB_SETTINGS_GET_WEBAUTHN_SUPPORT = 103;
+        int SET_PRELOADING_ENABLED = 104;
         // Remember to update AndroidXWebkitApiCall in enums.xml when adding new values here
-        int COUNT = 104;
+        int COUNT = 105;
     }
 
     public static void recordApiCall(@ApiCall int apiCall) {
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 46e9c9b..e5bf468 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -333,6 +333,8 @@
     "autotest_private_api_utils.cc",
     "birch/birch_client.h",
     "birch/birch_data_provider.h",
+    "birch/birch_icon_cache.cc",
+    "birch/birch_icon_cache.h",
     "birch/birch_item.cc",
     "birch/birch_item.h",
     "birch/birch_item_remover.cc",
@@ -1686,6 +1688,8 @@
     "system/focus_mode/sounds/playlist_view.h",
     "system/focus_mode/sounds/sound_section_view.cc",
     "system/focus_mode/sounds/sound_section_view.h",
+    "system/focus_mode/sounds/soundscape/soundscape_types.cc",
+    "system/focus_mode/sounds/soundscape/soundscape_types.h",
     "system/focus_mode/youtube_music/youtube_music_client.cc",
     "system/focus_mode/youtube_music/youtube_music_client.h",
     "system/focus_mode/youtube_music/youtube_music_controller.cc",
@@ -3549,6 +3553,7 @@
     "assistant/ui/main_stage/ui_element_container_view_unittest.cc",
     "assistant/util/deep_link_util_unittest.cc",
     "assistant/util/resource_util_unittest.cc",
+    "birch/birch_icon_cache_unittest.cc",
     "birch/birch_item_remover_unittest.cc",
     "birch/birch_item_unittest.cc",
     "birch/birch_model_unittest.cc",
@@ -3896,6 +3901,7 @@
     "system/focus_mode/focus_mode_task_test_utils.cc",
     "system/focus_mode/focus_mode_task_test_utils.h",
     "system/focus_mode/focus_mode_tray_unittest.cc",
+    "system/focus_mode/sounds/soundscape/soundscape_types_unittest.cc",
     "system/geolocation/geolocation_controller_test_util.cc",
     "system/geolocation/geolocation_controller_test_util.h",
     "system/geolocation/geolocation_controller_unittest.cc",
@@ -4823,6 +4829,8 @@
     "system/diagnostics/fake_diagnostics_browser_delegate.h",
     "system/diagnostics/log_test_helpers.cc",
     "system/diagnostics/log_test_helpers.h",
+    "system/focus_mode/sounds/soundscape/test/test_data.cc",
+    "system/focus_mode/sounds/soundscape/test/test_data.h",
     "system/focus_mode/test/test_focus_mode_delegate.cc",
     "system/focus_mode/test/test_focus_mode_delegate.h",
     "system/geolocation/test_geolocation_url_loader_factory.cc",
diff --git a/ash/birch/birch_icon_cache.cc b/ash/birch/birch_icon_cache.cc
new file mode 100644
index 0000000..e88fd09
--- /dev/null
+++ b/ash/birch/birch_icon_cache.cc
@@ -0,0 +1,31 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/birch/birch_icon_cache.h"
+
+#include <string>
+
+#include "base/containers/flat_map.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace ash {
+
+BirchIconCache::BirchIconCache() = default;
+
+BirchIconCache::~BirchIconCache() = default;
+
+gfx::ImageSkia BirchIconCache::Get(const std::string& url) {
+  auto it = map_.find(url);
+  if (it == map_.end()) {
+    // Return a null image if the URL was not found.
+    return gfx::ImageSkia();
+  }
+  return it->second;
+}
+
+void BirchIconCache::Put(const std::string& url, const gfx::ImageSkia& icon) {
+  map_[url] = icon;
+}
+
+}  // namespace ash
diff --git a/ash/birch/birch_icon_cache.h b/ash/birch/birch_icon_cache.h
new file mode 100644
index 0000000..b5e879ea
--- /dev/null
+++ b/ash/birch/birch_icon_cache.h
@@ -0,0 +1,42 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_BIRCH_BIRCH_ICON_CACHE_H_
+#define ASH_BIRCH_BIRCH_ICON_CACHE_H_
+
+#include <string>
+
+#include "ash/ash_export.h"
+#include "base/containers/flat_map.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace ash {
+
+// Caches icons downloaded from URLs. Birch has a very small number of icons so
+// there is no cache invalidation or removal of items from the cache. URLs are
+// encoded as strings as this is common in client code.
+class ASH_EXPORT BirchIconCache {
+ public:
+  BirchIconCache();
+  BirchIconCache(const BirchIconCache&) = delete;
+  BirchIconCache& operator=(const BirchIconCache&) = delete;
+  ~BirchIconCache();
+
+  // Gets an icon based on a URL. If the icon is not in the cache a null image
+  // is returned.
+  gfx::ImageSkia Get(const std::string& url);
+
+  // Adds or replaces an image in the cache.
+  void Put(const std::string& url, const gfx::ImageSkia& icon);
+
+  size_t size_for_test() const { return map_.size(); }
+
+ private:
+  // Maps a URL to an icon.
+  base::flat_map<std::string, gfx::ImageSkia> map_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_BIRCH_BIRCH_ICON_CACHE_H_
diff --git a/ash/birch/birch_icon_cache_unittest.cc b/ash/birch/birch_icon_cache_unittest.cc
new file mode 100644
index 0000000..482677ef
--- /dev/null
+++ b/ash/birch/birch_icon_cache_unittest.cc
@@ -0,0 +1,28 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/birch/birch_icon_cache.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_unittest_util.h"
+
+namespace ash {
+
+TEST(BirchIconCacheTest, NotFound) {
+  BirchIconCache cache;
+  gfx::ImageSkia icon = cache.Get("not-present");
+  EXPECT_TRUE(icon.isNull());
+}
+
+TEST(BirchIconCacheTest, Found) {
+  BirchIconCache cache;
+  gfx::ImageSkia input_icon = gfx::test::CreateImageSkia(16);
+  cache.Put("key", input_icon);
+  gfx::ImageSkia output_icon = cache.Get("key");
+  EXPECT_FALSE(output_icon.isNull());
+  EXPECT_TRUE(input_icon.BackedBySameObjectAs(output_icon));
+}
+
+}  // namespace ash
diff --git a/ash/birch/birch_item.cc b/ash/birch/birch_item.cc
index d757d5f..1f9ec31 100644
--- a/ash/birch/birch_item.cc
+++ b/ash/birch/birch_item.cc
@@ -8,6 +8,8 @@
 #include <sstream>
 #include <string>
 
+#include "ash/birch/birch_icon_cache.h"
+#include "ash/birch/birch_model.h"
 #include "ash/public/cpp/image_downloader.h"
 #include "ash/public/cpp/new_window_delegate.h"
 #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
@@ -32,8 +34,11 @@
 
 // Handles when an `image` is downloaded, by converting it to a ui::ImageModel
 // and running `callback`.
-void OnImageDownloaded(base::OnceCallback<void(const ui::ImageModel&)> callback,
+void OnImageDownloaded(const GURL& url,
+                       base::OnceCallback<void(const ui::ImageModel&)> callback,
                        const gfx::ImageSkia& image) {
+  // Add the image to the cache.
+  Shell::Get()->birch_model()->icon_cache()->Put(url.spec(), image);
   std::move(callback).Run(ui::ImageModel::FromImageSkia(image));
 }
 
@@ -47,6 +52,16 @@
     return;
   }
 
+  // Look for the icon in the cache.
+  gfx::ImageSkia icon =
+      Shell::Get()->birch_model()->icon_cache()->Get(url.spec());
+  if (!icon.isNull()) {
+    // Use the cached icon.
+    std::move(callback).Run(ui::ImageModel::FromImageSkia(icon));
+    return;
+  }
+
+  // Download the icon.
   const UserSession* active_user_session =
       Shell::Get()->session_controller()->GetUserSession(0);
   CHECK(active_user_session);
@@ -55,7 +70,7 @@
   ImageDownloader::Get()->Download(
       url, MISSING_TRAFFIC_ANNOTATION,
       active_user_session->user_info.account_id,
-      base::BindOnce(&OnImageDownloaded, std::move(callback)));
+      base::BindOnce(&OnImageDownloaded, url, std::move(callback)));
 }
 
 }  // namespace
diff --git a/ash/birch/birch_item_unittest.cc b/ash/birch/birch_item_unittest.cc
index 25e1d6f..2283c838 100644
--- a/ash/birch/birch_item_unittest.cc
+++ b/ash/birch/birch_item_unittest.cc
@@ -7,14 +7,20 @@
 #include <memory>
 #include <utility>
 
+#include "ash/birch/birch_icon_cache.h"
+#include "ash/birch/birch_model.h"
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/test/test_image_downloader.h"
 #include "ash/public/cpp/test/test_new_window_delegate.h"
+#include "ash/shell.h"
 #include "ash/system/time/calendar_unittest_utils.h"
 #include "ash/test/ash_test_base.h"
 #include "base/files/file_path.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_mock_clock_override.h"
+#include "base/test/test_future.h"
 #include "base/time/time.h"
 #include "chromeos/ash/components/settings/scoped_timezone_settings.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -459,7 +465,12 @@
 // The icon downloader requires ash::Shell, so use AshTestBase.
 class BirchItemIconTest : public AshTestBase {
  public:
+  BirchItemIconTest() {
+    feature_list_.InitAndEnableFeature(features::kForestFeature);
+  }
+
   TestImageDownloader image_downloader_;
+  base::test::ScopedFeatureList feature_list_;
 };
 
 TEST_F(BirchItemIconTest, Calendar_LoadIcon) {
@@ -482,8 +493,14 @@
                            /*end_time=*/base::Time(),
                            /*file_id=*/"");
 
-  item.LoadIcon(base::BindOnce(
-      [](const ui::ImageModel& icon) { EXPECT_FALSE(icon.IsEmpty()); }));
+  base::test::TestFuture<const ui::ImageModel&> future;
+  item.LoadIcon(future.GetCallback());
+  // The icon is not empty.
+  EXPECT_FALSE(future.Get().IsEmpty());
+
+  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
+  EXPECT_EQ(icon_cache->size_for_test(), 1u);
+  EXPECT_FALSE(icon_cache->Get("http://attachment.icon/").isNull());
 }
 
 TEST_F(BirchItemIconTest, Attachment_LoadIcon_InvalidUrl) {
@@ -494,8 +511,13 @@
                            /*end_time=*/base::Time(),
                            /*file_id=*/"");
 
-  item.LoadIcon(base::BindOnce(
-      [](const ui::ImageModel& icon) { EXPECT_TRUE(icon.IsEmpty()); }));
+  base::test::TestFuture<const ui::ImageModel&> future;
+  item.LoadIcon(future.GetCallback());
+  // The icon is empty.
+  EXPECT_TRUE(future.Get().IsEmpty());
+
+  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
+  EXPECT_EQ(icon_cache->size_for_test(), 0u);
 }
 
 TEST_F(BirchItemIconTest, Tab_LoadIcon) {
@@ -504,8 +526,14 @@
                     /*favicon_url=*/GURL("http://icon.com/"),
                     /*session_name=*/"",
                     BirchTabItem::DeviceFormFactor::kDesktop);
-  item.LoadIcon(base::BindOnce(
-      [](const ui::ImageModel& icon) { EXPECT_FALSE(icon.IsEmpty()); }));
+  base::test::TestFuture<const ui::ImageModel&> future;
+  item.LoadIcon(future.GetCallback());
+  // The icon is not empty.
+  EXPECT_FALSE(future.Get().IsEmpty());
+
+  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
+  EXPECT_EQ(icon_cache->size_for_test(), 1u);
+  EXPECT_FALSE(icon_cache->Get("http://icon.com/").isNull());
 }
 
 TEST_F(BirchItemIconTest, Tab_LoadIcon_InvalidUrl) {
@@ -514,8 +542,13 @@
                     /*favicon_url=*/GURL("invalid-url"),
                     /*session_name=*/"",
                     BirchTabItem::DeviceFormFactor::kDesktop);
-  item.LoadIcon(base::BindOnce(
-      [](const ui::ImageModel& icon) { EXPECT_TRUE(icon.IsEmpty()); }));
+  base::test::TestFuture<const ui::ImageModel&> future;
+  item.LoadIcon(future.GetCallback());
+  // The icon is empty.
+  EXPECT_TRUE(future.Get().IsEmpty());
+
+  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
+  EXPECT_EQ(icon_cache->size_for_test(), 0u);
 }
 
 TEST_F(BirchItemIconTest, Weather_LoadIcon) {
@@ -542,8 +575,14 @@
   BirchFileItem item(base::FilePath("/path/to/file.gdoc"), u"suggested",
                      base::Time(), "id_1", icon_url);
 
-  item.LoadIcon(base::BindOnce(
-      [](const ui::ImageModel& icon) { EXPECT_FALSE(icon.IsEmpty()); }));
+  base::test::TestFuture<const ui::ImageModel&> future;
+  item.LoadIcon(future.GetCallback());
+  // The icon is not empty.
+  EXPECT_FALSE(future.Get().IsEmpty());
+
+  auto* icon_cache = Shell::Get()->birch_model()->icon_cache();
+  EXPECT_EQ(icon_cache->size_for_test(), 1u);
+  EXPECT_FALSE(icon_cache->Get(icon_url).isNull());
 }
 
 }  // namespace
diff --git a/ash/birch/birch_model.cc b/ash/birch/birch_model.cc
index ad30ed8..949e90d 100644
--- a/ash/birch/birch_model.cc
+++ b/ash/birch/birch_model.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "ash/birch/birch_data_provider.h"
+#include "ash/birch/birch_icon_cache.h"
 #include "ash/birch/birch_item.h"
 #include "ash/birch/birch_item_remover.h"
 #include "ash/birch/birch_ranker.h"
@@ -58,7 +59,8 @@
       recent_tab_data_(prefs::kBirchUseRecentTabs, "Tab"),
       self_share_data_(prefs::kBirchUseSelfShare, "SelfShare"),
       release_notes_data_(prefs::kBirchUseReleaseNotes, "ReleaseNotes"),
-      weather_data_(prefs::kBirchUseWeather, "Weather") {
+      weather_data_(prefs::kBirchUseWeather, "Weather"),
+      icon_cache_(std::make_unique<BirchIconCache>()) {
   if (features::IsBirchWeatherEnabled()) {
     weather_provider_ = std::make_unique<BirchWeatherProvider>(this);
   }
diff --git a/ash/birch/birch_model.h b/ash/birch/birch_model.h
index 2e2f0d85..c18a9a3 100644
--- a/ash/birch/birch_model.h
+++ b/ash/birch/birch_model.h
@@ -24,6 +24,7 @@
 namespace ash {
 
 class BirchDataProvider;
+class BirchIconCache;
 class BirchItemRemover;
 
 // Birch model, which is used to aggregate and store relevant information from
@@ -105,6 +106,7 @@
   void SetClientAndInit(BirchClient* client);
 
   BirchClient* birch_client() { return birch_client_; }
+  BirchIconCache* icon_cache() { return icon_cache_.get(); }
 
   const std::vector<BirchCalendarItem>& GetCalendarItemsForTest() const {
     return calendar_data_.items;
@@ -225,6 +227,8 @@
 
   raw_ptr<BirchClient> birch_client_ = nullptr;
 
+  std::unique_ptr<BirchIconCache> icon_cache_;
+
   std::unique_ptr<BirchDataProvider> weather_provider_;
 
   // When set, this clock is used to ensure a consistent current time is used
diff --git a/ash/birch/birch_weather_provider.cc b/ash/birch/birch_weather_provider.cc
index 28dcaeb..65a9133a 100644
--- a/ash/birch/birch_weather_provider.cc
+++ b/ash/birch/birch_weather_provider.cc
@@ -7,6 +7,7 @@
 #include <string>
 
 #include "ash/ambient/ambient_controller.h"
+#include "ash/birch/birch_icon_cache.h"
 #include "ash/birch/birch_item.h"
 #include "ash/birch/birch_model.h"
 #include "ash/public/cpp/ambient/ambient_backend_controller.h"
@@ -116,18 +117,29 @@
     return;
   }
 
-  // Ideally we should avoid downloading from the same url again to reduce the
-  // overhead, as it's unlikely that the weather condition is changing
-  // frequently during the day.
+  // Check for a cached icon.
+  gfx::ImageSkia icon = Shell::Get()->birch_model()->icon_cache()->Get(
+      *weather_info->condition_icon_url);
+  if (!icon.isNull()) {
+    // Use the cached icon.
+    AddItemToBirchModel(base::UTF8ToUTF16(*weather_info->condition_description),
+                        *weather_info->temp_f, weather_info->show_celsius,
+                        icon);
+    return;
+  }
+
+  // Download the weather condition icon.
   DownloadImageFromUrl(
       *weather_info->condition_icon_url,
       base::BindOnce(&BirchWeatherProvider::OnWeatherConditionIconDownloaded,
                      weak_factory_.GetWeakPtr(),
+                     *weather_info->condition_icon_url,
                      base::UTF8ToUTF16(*weather_info->condition_description),
                      *weather_info->temp_f, weather_info->show_celsius));
 }
 
 void BirchWeatherProvider::OnWeatherConditionIconDownloaded(
+    const std::string& condition_icon_url,
     const std::u16string& weather_description,
     float temp_f,
     bool show_celsius,
@@ -137,6 +149,17 @@
     return;
   }
 
+  // Add the icon to the cache.
+  Shell::Get()->birch_model()->icon_cache()->Put(condition_icon_url, icon);
+
+  AddItemToBirchModel(weather_description, temp_f, show_celsius, icon);
+}
+
+void BirchWeatherProvider::AddItemToBirchModel(
+    const std::u16string& weather_description,
+    float temp_f,
+    bool show_celsius,
+    const gfx::ImageSkia& icon) {
   std::u16string temperature_string =
       show_celsius ? l10n_util::GetStringFUTF16Int(
                          IDS_ASH_AMBIENT_MODE_WEATHER_TEMPERATURE_IN_CELSIUS,
diff --git a/ash/birch/birch_weather_provider.h b/ash/birch/birch_weather_provider.h
index 00201f5..fde625f2 100644
--- a/ash/birch/birch_weather_provider.h
+++ b/ash/birch/birch_weather_provider.h
@@ -44,11 +44,18 @@
   // Callback to weather info icon request. It will update birch model with the
   // fetched weather info (including the downloaded weather icon).
   void OnWeatherConditionIconDownloaded(
+      const std::string& condition_icon_url,
       const std::u16string& weather_description,
       float temp_f,
       bool show_celsius,
       const gfx::ImageSkia& icon);
 
+  // Adds the weather item to the birch model.
+  void AddItemToBirchModel(const std::u16string& weather_description,
+                           float temp_f,
+                           bool show_celsius,
+                           const gfx::ImageSkia& icon);
+
   const raw_ptr<BirchModel> birch_model_;
   bool is_fetching_ = false;
 
diff --git a/ash/birch/birch_weather_provider_unittest.cc b/ash/birch/birch_weather_provider_unittest.cc
index b32cfe4..10ffce6 100644
--- a/ash/birch/birch_weather_provider_unittest.cc
+++ b/ash/birch/birch_weather_provider_unittest.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "ash/ambient/ambient_controller.h"
+#include "ash/birch/birch_icon_cache.h"
 #include "ash/birch/birch_model.h"
 #include "ash/constants/ash_features.h"
 #include "ash/constants/geolocation_access_level.h"
@@ -384,4 +385,64 @@
   EXPECT_EQ(ambient_backend_controller_->fetch_weather_count(), 1);
 }
 
+TEST_F(BirchWeatherProviderTest, IconCache) {
+  auto* birch_model = Shell::Get()->birch_model();
+  auto* icon_cache = birch_model->icon_cache();
+
+  // The cache starts empty.
+  EXPECT_EQ(icon_cache->size_for_test(), 0u);
+
+  // Fetch weather.
+  WeatherInfo info1;
+  info1.condition_description = "Cloudy";
+  info1.condition_icon_url = "https://cloudy-icon-url";
+  info1.show_celsius = false;
+  info1.temp_f = 70.0f;
+  ambient_backend_controller_->SetWeatherInfo(info1);
+
+  base::RunLoop run_loop;
+  birch_model->RequestBirchDataFetch(/*is_post_login=*/false,
+                                     run_loop.QuitClosure());
+  run_loop.Run();
+
+  // The icon cache has a single item in it.
+  EXPECT_EQ(icon_cache->size_for_test(), 1u);
+  EXPECT_FALSE(icon_cache->Get("https://cloudy-icon-url").isNull());
+
+  // Fetch again with the same icon URL.
+  WeatherInfo info2;
+  info2.condition_description = "Cloudy";
+  info2.condition_icon_url = "https://cloudy-icon-url";
+  info2.show_celsius = false;
+  info2.temp_f = 70.0f;
+  ambient_backend_controller_->SetWeatherInfo(info2);
+
+  base::RunLoop run_loop2;
+  birch_model->RequestBirchDataFetch(/*is_post_login=*/false,
+                                     run_loop2.QuitClosure());
+  run_loop2.Run();
+
+  // The cache still has a single item in it because the icon was the same.
+  EXPECT_EQ(icon_cache->size_for_test(), 1u);
+  EXPECT_FALSE(icon_cache->Get("https://cloudy-icon-url").isNull());
+
+  // Fetch again with a different icon URL.
+  WeatherInfo info3;
+  info2.condition_description = "Sunny";
+  info2.condition_icon_url = "https://sunny-icon-url";
+  info2.show_celsius = false;
+  info2.temp_f = 73.0f;
+  ambient_backend_controller_->SetWeatherInfo(info2);
+
+  base::RunLoop run_loop3;
+  birch_model->RequestBirchDataFetch(/*is_post_login=*/false,
+                                     run_loop3.QuitClosure());
+  run_loop3.Run();
+
+  // Cache now has two items in it for the two different icons.
+  EXPECT_EQ(icon_cache->size_for_test(), 2u);
+  EXPECT_FALSE(icon_cache->Get("https://cloudy-icon-url").isNull());
+  EXPECT_FALSE(icon_cache->Get("https://sunny-icon-url").isNull());
+}
+
 }  // namespace ash
diff --git a/ash/login/SAML_INTEGRATION_OWNERS b/ash/login/SAML_INTEGRATION_OWNERS
index 64ef0f5..530660c 100644
--- a/ash/login/SAML_INTEGRATION_OWNERS
+++ b/ash/login/SAML_INTEGRATION_OWNERS
@@ -1,2 +1,3 @@
 andreydav@google.com
+ayag@chromium.org
 lmasopust@google.com
diff --git a/ash/search_box/search_box_view_base.cc b/ash/search_box/search_box_view_base.cc
index f0af52e..24ecc2ac 100644
--- a/ash/search_box/search_box_view_base.cc
+++ b/ash/search_box/search_box_view_base.cc
@@ -657,7 +657,7 @@
                                    .height()
                              : 0;
   return gfx::Size(kSearchBoxPreferredWidth,
-                   kSearchBoxPreferredHeight + iph_height);
+                   kSearchBoxPreferredHeight + insets.height() + iph_height);
 }
 
 void SearchBoxViewBase::OnEnabledChanged() {
diff --git a/ash/system/focus_mode/sounds/soundscape/README.md b/ash/system/focus_mode/sounds/soundscape/README.md
new file mode 100644
index 0000000..01246c3
--- /dev/null
+++ b/ash/system/focus_mode/sounds/soundscape/README.md
@@ -0,0 +1,8 @@
+# Focus Sounds
+
+Soundscapes is the internal name for Focus Sounds. Soundscapes are the
+playlists that are only used by Focus Mode and independent of the other
+sound options in Focus Mode.
+
+This folder contains code relevant to downloading and parsing the data for
+this backend.
diff --git a/ash/system/focus_mode/sounds/soundscape/soundscape_types.cc b/ash/system/focus_mode/sounds/soundscape/soundscape_types.cc
new file mode 100644
index 0000000..9a861015
--- /dev/null
+++ b/ash/system/focus_mode/sounds/soundscape/soundscape_types.cc
@@ -0,0 +1,229 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/focus_mode/sounds/soundscape/soundscape_types.h"
+
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/values.h"
+
+namespace ash {
+
+namespace {
+
+// Maximum allowed length of parsed strings.
+constexpr int kStringMax = 255;
+
+// Maximum number of tracks per playlist.
+constexpr int kMaxTracks = 100;
+
+// For the UI, there should always be 4 playlists in a valid configuration.
+constexpr int kNumPlaylists = 4;
+
+constexpr char kDefaultLocale[] = "en-US";
+
+bool ValidString(const std::string* str) {
+  return !!str && !str->empty() && str->length() <= kStringMax;
+}
+
+bool ValidList(const base::Value::List* list) {
+  return !!list && !list->empty();
+}
+
+// Returns the best name for `locale` in `localized_names` which is assumed to
+// be a Dict with a "locale" and "name" field. If a match for `locale` is not
+// found, returns the result for the default locale (en-US). If a suitable name
+// is not found, nullptr is returned.
+const std::string* BestLocalizedName(std::string_view locale,
+                                     const base::Value::List& localized_names) {
+  const std::string* default_name = nullptr;
+  const std::string* language_match = nullptr;
+
+  for (const base::Value& localized_name : localized_names) {
+    const base::Value::Dict* dict = localized_name.GetIfDict();
+    if (!dict) {
+      continue;
+    }
+    const std::string* candidate_locale = dict->FindString("locale");
+    if (!candidate_locale) {
+      continue;
+    }
+    const std::string* candidate_name = dict->FindString("name");
+    if (!ValidString(candidate_name)) {
+      continue;
+    }
+
+    std::string_view locale_view(*candidate_locale);
+    if (locale_view == locale) {
+      // Exact matches are best. We're done.
+      return candidate_name;
+    }
+
+    if (locale_view.substr(0, 2) == locale.substr(0, 2)) {
+      // The first 2 letters of the locale represent the language. Try to match
+      // this in the case that there is no exact match.
+      language_match = candidate_name;
+      continue;
+    }
+
+    if (locale_view == kDefaultLocale) {
+      default_name = candidate_name;
+      continue;
+    }
+  }
+
+  if (language_match) {
+    return language_match;
+  }
+
+  return default_name;
+}
+
+}  // namespace
+
+// static
+std::optional<SoundscapeTrack> SoundscapeTrack::FromValue(
+    const base::Value& value) {
+  const base::Value::Dict* dict = value.GetIfDict();
+  if (!dict) {
+    return std::nullopt;
+  }
+
+  const std::string* name = dict->FindString("name");
+  const std::string* path = dict->FindString("path");
+
+  if (!ValidString(name) || !ValidString(path)) {
+    return std::nullopt;
+  }
+
+  return SoundscapeTrack(*name, *path);
+}
+
+SoundscapeTrack::SoundscapeTrack(const std::string& name,
+                                 const std::string& path)
+    : name(name), path(path) {}
+SoundscapeTrack::SoundscapeTrack(const SoundscapeTrack&) = default;
+SoundscapeTrack::~SoundscapeTrack() = default;
+
+// static
+std::optional<SoundscapePlaylist> SoundscapePlaylist::FromValue(
+    std::string_view locale,
+    const base::Value& value) {
+  if (locale.length() != 2 && locale.length() != 5) {
+    // Locales are either 2 or 5 characters representing just the language (e.g.
+    // "en") or the language and country with a dash ("en-US") as described in
+    // RFC 5646.
+    return std::nullopt;
+  }
+
+  const base::Value::Dict* dict = value.GetIfDict();
+  if (!dict) {
+    return std::nullopt;
+  }
+
+  const base::Value::List* localized_names = dict->FindList("name");
+  const std::string* thumbnail = dict->FindString("thumbnail");
+  const std::string* uuid = dict->FindString("uuid");
+  const base::Value::List* tracks = dict->FindList("tracks");
+
+  if (!ValidList(localized_names) || !ValidList(tracks) ||
+      !ValidString(thumbnail) || !ValidString(uuid)) {
+    return std::nullopt;
+  }
+
+  if (tracks->size() > kMaxTracks) {
+    LOG(WARNING) << "Too many tracks in playlist " << tracks->size();
+    return std::nullopt;
+  }
+
+  const std::string* name = BestLocalizedName(locale, *localized_names);
+  if (!name) {
+    return std::nullopt;
+  }
+
+  base::Uuid parsed_uuid = base::Uuid::ParseLowercase(*uuid);
+  if (!parsed_uuid.is_valid()) {
+    return std::nullopt;
+  }
+
+  SoundscapePlaylist playlist;
+  playlist.tracks.reserve(tracks->size());
+  for (const base::Value& track : *tracks) {
+    std::optional<SoundscapeTrack> parsed_track =
+        SoundscapeTrack::FromValue(track);
+    if (!parsed_track) {
+      LOG(WARNING) << "Track validation failed";
+      return std::nullopt;
+    }
+    playlist.tracks.push_back(std::move(*parsed_track));
+  }
+
+  playlist.name = *name;
+  playlist.uuid = parsed_uuid;
+  playlist.thumbnail = *thumbnail;
+
+  return playlist;
+}
+
+SoundscapePlaylist::SoundscapePlaylist() = default;
+SoundscapePlaylist::SoundscapePlaylist(SoundscapePlaylist&&) = default;
+SoundscapePlaylist::~SoundscapePlaylist() = default;
+
+SoundscapeConfiguration::SoundscapeConfiguration() = default;
+SoundscapeConfiguration::SoundscapeConfiguration(SoundscapeConfiguration&&) =
+    default;
+SoundscapeConfiguration& SoundscapeConfiguration::operator=(
+    SoundscapeConfiguration&&) = default;
+SoundscapeConfiguration::~SoundscapeConfiguration() = default;
+
+// static
+std::optional<SoundscapeConfiguration>
+SoundscapeConfiguration::ParseConfiguration(std::string_view locale,
+                                            std::string_view json) {
+  if (locale.size() != 2u && locale.size() != 5u) {
+    LOG(ERROR) << "Invalid locale string";
+    return std::nullopt;
+  }
+
+  if (json.empty()) {
+    return std::nullopt;
+  }
+
+  std::optional<base::Value> value =
+      base::JSONReader::Read(json, base::JSONParserOptions::JSON_PARSE_RFC);
+  if (!value) {
+    LOG(WARNING) << "Configuration cannot be parsed";
+    return std::nullopt;
+  }
+
+  const base::Value::Dict* dict = value->GetIfDict();
+  if (!dict) {
+    LOG(WARNING) << "Configuration is not a dictionary";
+    return std::nullopt;
+  }
+
+  const base::Value::List* playlists = dict->FindList("playlists");
+  // We always expect exactly 4 playlists.
+  if (!playlists || playlists->size() != kNumPlaylists) {
+    LOG(WARNING) << "Playlists are invalid";
+    return std::nullopt;
+  }
+
+  SoundscapeConfiguration config;
+  config.playlists.reserve(kNumPlaylists);
+  for (const base::Value& playlist : *playlists) {
+    std::optional<SoundscapePlaylist> parsed =
+        SoundscapePlaylist::FromValue(locale, playlist);
+    if (!parsed) {
+      LOG(WARNING) << "Playlist validation failed";
+      return std::nullopt;
+    }
+    config.playlists.push_back(std::move(*parsed));
+  }
+
+  return config;
+}
+
+}  // namespace ash
diff --git a/ash/system/focus_mode/sounds/soundscape/soundscape_types.h b/ash/system/focus_mode/sounds/soundscape/soundscape_types.h
new file mode 100644
index 0000000..e7e2cff
--- /dev/null
+++ b/ash/system/focus_mode/sounds/soundscape/soundscape_types.h
@@ -0,0 +1,83 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_FOCUS_MODE_SOUNDS_SOUNDSCAPE_SOUNDSCAPE_TYPES_H_
+#define ASH_SYSTEM_FOCUS_MODE_SOUNDS_SOUNDSCAPE_SOUNDSCAPE_TYPES_H_
+
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "base/uuid.h"
+
+namespace base {
+class Value;
+}  // namespace base
+
+namespace ash {
+
+struct ASH_EXPORT SoundscapeTrack {
+  // If possible, returns a fully populated `SoundscapeTrack` from `value`.
+  // Otherwise, nullopt.
+  static std::optional<SoundscapeTrack> FromValue(const base::Value& value);
+
+  SoundscapeTrack(const std::string& name, const std::string& path);
+  SoundscapeTrack(const SoundscapeTrack&);
+  ~SoundscapeTrack();
+
+  std::string name;
+
+  // Relative path to the audio file. Relative to the configuration origin.
+  std::string path;
+};
+
+struct ASH_EXPORT SoundscapePlaylist {
+  // Returns a fully populated `SoundscapePlaylist` from `value` if all fields
+  // are available. Uses the name for `locale` if found. Otherwise, names
+  // default to "en-US". If `value` does not generate a fully populated
+  // `SoundscapePlaylist`, returns nullptr.
+  static std::optional<SoundscapePlaylist> FromValue(std::string_view locale,
+                                                     const base::Value& value);
+
+  SoundscapePlaylist();
+  SoundscapePlaylist(const SoundscapePlaylist&) = delete;
+  SoundscapePlaylist(SoundscapePlaylist&&);
+  ~SoundscapePlaylist();
+
+  // Uniquely identifies a playlist. Playlists which share a Uuid will have the
+  // same contents.
+  base::Uuid uuid;
+
+  std::string name;
+
+  // A relative path to the thumbnail image (relative to the origin url of
+  // the configuration).
+  std::string thumbnail;
+  std::vector<SoundscapeTrack> tracks;
+};
+
+// Represents a configuration for Soundscapes consisting of a set of
+// playlists which are made of a set of tracks which can be played while
+// a user is in a Focus Mode session.
+struct ASH_EXPORT SoundscapeConfiguration {
+  // Attempts to build a `SoundscapeConfiguration` from `json` parsed as JSON.
+  // If any fields are missing or invalid, returns nullopt.
+  static std::optional<SoundscapeConfiguration> ParseConfiguration(
+      std::string_view locale,
+      std::string_view json);
+
+  SoundscapeConfiguration();
+  SoundscapeConfiguration(const SoundscapeConfiguration&) = delete;
+  SoundscapeConfiguration(SoundscapeConfiguration&&);
+  SoundscapeConfiguration& operator=(SoundscapeConfiguration&&);
+  ~SoundscapeConfiguration();
+
+  std::vector<SoundscapePlaylist> playlists;
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_FOCUS_MODE_SOUNDS_SOUNDSCAPE_SOUNDSCAPE_TYPES_H_
diff --git a/ash/system/focus_mode/sounds/soundscape/soundscape_types_unittest.cc b/ash/system/focus_mode/sounds/soundscape/soundscape_types_unittest.cc
new file mode 100644
index 0000000..64bbc1ef
--- /dev/null
+++ b/ash/system/focus_mode/sounds/soundscape/soundscape_types_unittest.cc
@@ -0,0 +1,164 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/focus_mode/sounds/soundscape/soundscape_types.h"
+
+#include <optional>
+
+#include "ash/system/focus_mode/sounds/soundscape/test/test_data.h"
+#include "base/values.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+namespace {
+
+constexpr char kLocale[] = "en-US";
+constexpr char kTrackUrl[] = "/tracks/track.mp3";
+constexpr char kUuid[] = "bee58263-412c-4f6c-9eb5-7abacc89b2e8";
+
+base::Value TestTrack() {
+  base::Value::Dict dict;
+  dict.Set("name", "Test Track");
+  dict.Set("path", kTrackUrl);
+
+  // The JSON format supports an artist field but it's currently unused.
+  dict.Set("artist", "Not parsed");
+  return base::Value(std::move(dict));
+  ;
+}
+
+base::Value TestPlaylist() {
+  base::Value::List track_list;
+  for (int i = 0; i < 3; i++) {
+    track_list.Append(TestTrack());
+  }
+
+  base::Value::List name_list =
+      base::Value::List()
+          .Append(base::Value::Dict()
+                      .Set("locale", "en-US")
+                      .Set("name", "World's Most Awesome Playlist"))
+          .Append(
+              base::Value::Dict()
+                  .Set("locale", "es-US")
+                  .Set("name",
+                       "La lista de reproducción más impresionante del mundo"));
+  base::Value::Dict dict;
+  dict.Set("uuid", kUuid);
+  dict.Set("name", std::move(name_list));
+  dict.Set("thumbnail", "/thumbs/pic_123453.png");
+  dict.Set("tracks", std::move(track_list));
+  return base::Value(std::move(dict));
+}
+
+TEST(SoundscapeTypeTests, ParseTrack) {
+  base::Value test_value = TestTrack();
+  std::optional<SoundscapeTrack> track = SoundscapeTrack::FromValue(test_value);
+  ASSERT_TRUE(track);
+  EXPECT_THAT(track->name, testing::Eq("Test Track"));
+  EXPECT_THAT(track->path, testing::Eq(kTrackUrl));
+}
+
+TEST(SoundscapeTypeTests, ParseTrack_Empty) {
+  // Not a dictionary.
+  EXPECT_THAT(
+      SoundscapeTrack::FromValue(base::Value(base::Value::Type::STRING)),
+      testing::Eq(std::nullopt));
+  // Empty dictionary.
+  EXPECT_THAT(SoundscapeTrack::FromValue(base::Value(base::Value::Type::DICT)),
+              testing::Eq(std::nullopt));
+}
+
+TEST(SoundscapeTypeTests, ParsePlaylist) {
+  base::Value test_value = TestPlaylist();
+  std::optional<SoundscapePlaylist> playlist =
+      SoundscapePlaylist::FromValue(kLocale, test_value);
+  ASSERT_TRUE(playlist);
+  EXPECT_THAT(playlist->name, testing::Eq("World's Most Awesome Playlist"));
+
+  EXPECT_TRUE(playlist->uuid.is_valid());
+  EXPECT_THAT(playlist->uuid, testing::Eq(base::Uuid::ParseLowercase(kUuid)));
+
+  EXPECT_THAT(playlist->tracks, testing::SizeIs(3));
+  EXPECT_THAT(playlist->tracks,
+              testing::Each(testing::Field(&SoundscapeTrack::name,
+                                           testing::Eq("Test Track"))));
+}
+
+TEST(SoundscapeTypeTests, ParsePlaylist_NonEnglish) {
+  base::Value test_value = TestPlaylist();
+  std::optional<SoundscapePlaylist> playlist =
+      SoundscapePlaylist::FromValue("es-US", test_value);
+  ASSERT_TRUE(playlist);
+  EXPECT_THAT(
+      playlist->name,
+      testing::Eq("La lista de reproducción más impresionante del mundo"));
+}
+
+TEST(SoundscapeTypeTests, ParsePlaylist_EnglishFallback) {
+  base::Value test_value = TestPlaylist();
+  // ZZ is a user defined country code and ia is the language code for
+  // Interlingua (for which we do not have a mapping).
+  const std::string locale = "ia-ZZ";
+  std::optional<SoundscapePlaylist> playlist =
+      SoundscapePlaylist::FromValue(locale, test_value);
+  ASSERT_TRUE(playlist);
+  EXPECT_THAT(playlist->name, testing::Eq("World's Most Awesome Playlist"));
+}
+
+TEST(SoundscapeTypeTests, ParsePlaylist_Empty) {
+  EXPECT_THAT(SoundscapePlaylist::FromValue(
+                  kLocale, base::Value(base::Value::Type::NONE)),
+              testing::Eq(std::nullopt));
+  EXPECT_THAT(SoundscapePlaylist::FromValue(
+                  kLocale, base::Value(base::Value::Type::DICT)),
+              testing::Eq(std::nullopt));
+}
+
+TEST(SoundscapeTypeTests, ParseConfiguration) {
+  std::optional<SoundscapeConfiguration> config =
+      SoundscapeConfiguration::ParseConfiguration("fr-CA", kTestConfig);
+  ASSERT_TRUE(config);
+  EXPECT_THAT(config->playlists, testing::SizeIs(4));
+  EXPECT_THAT(
+      config->playlists,
+      testing::ElementsAre(
+          testing::Field(&SoundscapePlaylist::name, "Musique Classique"),
+          testing::Field(&SoundscapePlaylist::name, "Nature"),
+          testing::Field(&SoundscapePlaylist::name, "Flux"),
+          testing::Field(&SoundscapePlaylist::name, "Ambiance")));
+}
+
+TEST(SoundscapeTypeTests, ParseConfiguration_LangOnly) {
+  std::optional<SoundscapeConfiguration> config =
+      SoundscapeConfiguration::ParseConfiguration("fr", kTestConfig);
+  ASSERT_TRUE(config);
+  EXPECT_THAT(config->playlists, testing::SizeIs(4));
+  EXPECT_THAT(config->playlists,
+              testing::Contains(testing::Field(&SoundscapePlaylist::name,
+                                               "Musique Classique")));
+}
+
+TEST(SoundscapeTypeTests, ParseConfiguration_Empty) {
+  std::optional<SoundscapeConfiguration> config =
+      SoundscapeConfiguration::ParseConfiguration(kLocale, "");
+  EXPECT_THAT(config, testing::Eq(std::nullopt));
+}
+
+TEST(SoundscapeTypeTests, ParseConfiguration_Malformed) {
+  std::optional<SoundscapeConfiguration> config =
+      SoundscapeConfiguration::ParseConfiguration(kLocale, "{lksjdfksjdfj}");
+  EXPECT_THAT(config, testing::Eq(std::nullopt));
+}
+
+TEST(SoundscapeTypeTests, ParseConfiguration_InvalidLocale) {
+  std::optional<SoundscapeConfiguration> config =
+      SoundscapeConfiguration::ParseConfiguration("foo", kTestConfig);
+  EXPECT_THAT(config, testing::Eq(std::nullopt));
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/ash/system/focus_mode/sounds/soundscape/test/test_data.cc b/ash/system/focus_mode/sounds/soundscape/test/test_data.cc
new file mode 100644
index 0000000..bf17072
--- /dev/null
+++ b/ash/system/focus_mode/sounds/soundscape/test/test_data.cc
@@ -0,0 +1,121 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/focus_mode/sounds/soundscape/test/test_data.h"
+
+namespace ash {
+
+constexpr char kTestConfig[] = R"(
+{
+  "version": "20240426",
+  "playlists": [
+    {
+      "name": [
+        {
+          "locale": "en-US",
+          "name": "Classical"
+        },
+        {
+          "locale": "fr-CA",
+          "name": "Musique Classique"
+        }
+      ],
+      "uuid": "a58d4418-c835-4254-b5d6-cca909046202",
+      "thumbnail": "/thumbnails/classical_20240426.png",
+      "tracks": [
+        {
+          "name": "Violins",
+          "artist": "Artist",
+          "path": "/tracks/violins_20240426.mp3"
+        },
+        {
+          "name": "Cello",
+          "artist": "Artist",
+          "path": "/tracks/cello_20240426.mp3"
+        }
+      ]
+    },
+    {
+      "name": [
+        {
+          "locale": "en-US",
+          "name": "Nature"
+        },
+        {
+          "locale": "fr-CA",
+          "name": "Nature"
+        }
+      ],
+      "uuid": "1c25911f-d847-4500-94e3-6e2d9a52846a",
+      "thumbnail": "/thumbnails/nature_20240426.png",
+      "tracks": [
+        {
+          "name": "Bears",
+          "artist": "Artist",
+          "path": "/tracks/bears_20240426.mp3"
+        },
+        {
+          "name": "Penguins",
+          "artist": "Artist",
+          "path": "/tracks/penguins_20240426.mp3"
+        }
+      ]
+    },
+    {
+      "name": [
+        {
+          "locale": "en-US",
+          "name": "Flow"
+        },
+        {
+          "locale": "fr-CA",
+          "name": "Flux"
+        }
+      ],
+      "uuid": "921c4f5c-ccd8-4f1a-ba19-0b3c2685aa92",
+      "thumbnail": "/thumbnails/flow_20240426.png",
+      "tracks": [
+        {
+          "name": "A Longer Song Name",
+          "artist": "Artist",
+          "path": "/tracks/track_20240426.mp3"
+        },
+        {
+          "name": "Release",
+          "artist": "Artist",
+          "path": "/tracks/release_20240426.mp3"
+        }
+      ]
+    },
+    {
+      "name": [
+        {
+          "locale": "en-US",
+          "name": "Ambiance"
+        },
+        {
+          "locale": "fr-CA",
+          "name": "Ambiance"
+        }
+      ],
+      "uuid": "1b12ce3a-4857-4aae-95df-65cef1c4d4f3",
+      "thumbnail": "/thumbnails/ambiance_20240426.png",
+      "tracks": [
+        {
+          "name": "Rain",
+          "artist": "Artist",
+          "path": "/tracks/rain_20240426.mp3"
+        },
+        {
+          "name": "Coffee Shop",
+          "artist": "Artist",
+          "path": "/tracks/coffee_shop_20240426.mp3"
+        }
+      ]
+    }
+  ]
+}
+)";
+
+}  // namespace ash
diff --git a/ash/system/focus_mode/sounds/soundscape/test/test_data.h b/ash/system/focus_mode/sounds/soundscape/test/test_data.h
new file mode 100644
index 0000000..00ee9eca
--- /dev/null
+++ b/ash/system/focus_mode/sounds/soundscape/test/test_data.h
@@ -0,0 +1,14 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_FOCUS_MODE_SOUNDS_SOUNDSCAPE_TEST_TEST_DATA_H_
+#define ASH_SYSTEM_FOCUS_MODE_SOUNDS_SOUNDSCAPE_TEST_TEST_DATA_H_
+
+namespace ash {
+
+extern const char kTestConfig[];
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_FOCUS_MODE_SOUNDS_SOUNDSCAPE_TEST_TEST_DATA_H_
diff --git a/ash/system/notification_center/views/notification_list_view.cc b/ash/system/notification_center/views/notification_list_view.cc
index 8db63f0..a971e76 100644
--- a/ash/system/notification_center/views/notification_list_view.cc
+++ b/ash/system/notification_center/views/notification_list_view.cc
@@ -744,7 +744,7 @@
     // Height is taken from preferred size, which is calculated based on the
     // tween and animation state when animations are occurring. So views which
     // are animating will provide the correct interpolated height here.
-    const int height = view->GetHeightForWidth(message_view_width_);
+    const int height = view->CalculateHeight();
     const int direction = view->GetSlideDirection();
 
     if (y > 0) {
diff --git a/ash/webui/print_preview_cros/resources/BUILD.gn b/ash/webui/print_preview_cros/resources/BUILD.gn
index 0b89b338..075ab049 100644
--- a/ash/webui/print_preview_cros/resources/BUILD.gn
+++ b/ash/webui/print_preview_cros/resources/BUILD.gn
@@ -16,6 +16,7 @@
   css_files = [ "css/print_preview_cros_shared.css" ]
 
   non_web_component_files = [
+    "js/data/capabilities_manager.ts",
     "js/data/destination_constants.ts",
     "js/data/destination_manager.ts",
     "js/data/preview_ticket_manager.ts",
diff --git a/ash/webui/print_preview_cros/resources/js/data/capabilities_manager.ts b/ash/webui/print_preview_cros/resources/js/data/capabilities_manager.ts
new file mode 100644
index 0000000..c74e0d2
--- /dev/null
+++ b/ash/webui/print_preview_cros/resources/js/data/capabilities_manager.ts
@@ -0,0 +1,131 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert} from 'chrome://resources/js/assert.js';
+import {EventTracker} from 'chrome://resources/js/event_tracker.js';
+
+import {createCustomEvent} from '../utils/event_utils.js';
+import {getDestinationProvider} from '../utils/mojo_data_providers.js';
+import {Capabilities, type DestinationProvider, SessionContext} from '../utils/print_preview_cros_app_types.js';
+
+import {DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED, DestinationManager} from './destination_manager.js';
+
+/**
+ * @fileoverview
+ * 'capabilties_manager' responsible for requesting and storing the printing
+ * capabilities for the active destination.
+ */
+
+export const CAPABILITIES_MANAGER_SESSION_INITIALIZED =
+    'capabilities-manager.session-initialized';
+export const CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_LOADING =
+    'capabilities-manager.active-destination-caps-loading';
+export const CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_READY =
+    'capabilities-manager.active-destination-caps-ready';
+
+export class CapabilitiesManager extends EventTarget {
+  private static instance: CapabilitiesManager|null = null;
+
+  static getInstance(): CapabilitiesManager {
+    if (CapabilitiesManager.instance === null) {
+      CapabilitiesManager.instance = new CapabilitiesManager();
+    }
+
+    return CapabilitiesManager.instance;
+  }
+
+  static resetInstanceForTesting(): void {
+    CapabilitiesManager.instance = null;
+  }
+
+  // Non-static properties:
+  private destinationProvider: DestinationProvider|null;
+  private sessionContext: SessionContext;
+  private eventTracker = new EventTracker();
+  private destinationManager: DestinationManager =
+      DestinationManager.getInstance();
+  private capabilitiesCache = new Map<string, Capabilities>();
+
+  // Prevent additional initialization.
+  private constructor() {
+    super();
+
+    // Setup mojo data providers.
+    this.destinationProvider = getDestinationProvider();
+
+    this.eventTracker.add(
+        this.destinationManager, DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED,
+        (): void => this.fetchCapabilitesForActiveDestination());
+  }
+
+  // `initializeSession` is only intended to be called once from the
+  // `PrintPreviewCrosAppController`.
+  initializeSession(sessionContext: SessionContext): void {
+    assert(
+        !this.sessionContext, 'SessionContext should only be configured once');
+    this.sessionContext = sessionContext;
+
+    this.dispatchEvent(
+        createCustomEvent(CAPABILITIES_MANAGER_SESSION_INITIALIZED));
+  }
+
+  private fetchCapabilitesForActiveDestination(): void {
+    const destination = this.destinationManager.getActiveDestination();
+    if (destination === null) {
+      return;
+    }
+
+    this.dispatchEvent(createCustomEvent(
+        CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_LOADING));
+
+    const cachedCapabilities = this.capabilitiesCache.get(destination.id);
+    if (cachedCapabilities) {
+      this.dispatchEvent(createCustomEvent(
+          CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_READY));
+      return;
+    }
+
+    this.destinationProvider!
+        .fetchCapabilities(destination.id, destination.printerType)
+        .then((caps: Capabilities) => this.onCapabilitiesFetched(caps));
+  }
+
+  private onCapabilitiesFetched(caps: Capabilities): void {
+    this.capabilitiesCache.set(caps.destinationId, caps);
+
+    // Since multiple capabilities requests can be in-flight simultaneously,
+    // verify this capabilities response belongs to the active destination
+    // before sending the ready event.
+    const activeDestination = this.destinationManager.getActiveDestination();
+    assert(activeDestination);
+    if (caps.destinationId === activeDestination.id) {
+      this.dispatchEvent(createCustomEvent(
+          CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_READY));
+    }
+  }
+
+  // Returns the capabilities from the active destination if available.
+  getActiveDestinationCapabilities(): Capabilities|undefined {
+    const activeDestination = this.destinationManager.getActiveDestination();
+    if (activeDestination === null) {
+      return undefined;
+    }
+
+    return this.capabilitiesCache.get(activeDestination.id);
+  }
+
+  // Returns true only after the `initializeSession` function has been called
+  // with a valid `SessionContext`.
+  isSessionInitialized(): boolean {
+    return !!this.sessionContext;
+  }
+}
+
+declare global {
+  interface HTMLElementEventMap {
+    [CAPABILITIES_MANAGER_SESSION_INITIALIZED]: CustomEvent<void>;
+    [CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_LOADING]: CustomEvent<void>;
+    [CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_READY]: CustomEvent<void>;
+  }
+}
diff --git a/ash/webui/print_preview_cros/resources/js/fakes/fake_data.ts b/ash/webui/print_preview_cros/resources/js/fakes/fake_data.ts
index bc98375..cf350a8 100644
--- a/ash/webui/print_preview_cros/resources/js/fakes/fake_data.ts
+++ b/ash/webui/print_preview_cros/resources/js/fakes/fake_data.ts
@@ -45,7 +45,8 @@
 }
 
 
-export function getFakeCapabilities(destinationId: string = ''): Capabilities {
+export function getFakeCapabilities(destinationId: string = 'Printer1'):
+    Capabilities {
   const collate: CollateCapability = {
     valueDefault: true,
   };
diff --git a/ash/webui/print_preview_cros/resources/js/print_preview_cros_app_controller.ts b/ash/webui/print_preview_cros/resources/js/print_preview_cros_app_controller.ts
index 22a8fca..799da5f 100644
--- a/ash/webui/print_preview_cros/resources/js/print_preview_cros_app_controller.ts
+++ b/ash/webui/print_preview_cros/resources/js/print_preview_cros_app_controller.ts
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {CapabilitiesManager} from './data/capabilities_manager.js';
 import {DestinationManager} from './data/destination_manager.js';
 import {PreviewTicketManager} from './data/preview_ticket_manager.js';
 import {PrintTicketManager} from './data/print_ticket_manager.js';
@@ -19,6 +20,7 @@
 export class PrintPreviewCrosAppController extends EventTarget {
   private printPreviewPageHandler = getPrintPreviewPageHandler();
   private sessionContext: SessionContext;
+  private capabilitiesManager = CapabilitiesManager.getInstance();
   private destinationManager = DestinationManager.getInstance();
   private previewTicketManager = PreviewTicketManager.getInstance();
   private printTicketManager = PrintTicketManager.getInstance();
@@ -29,6 +31,7 @@
     this.printPreviewPageHandler.startSession().then(
         (sessionContext: SessionContext): void => {
           this.sessionContext = sessionContext;
+          this.capabilitiesManager.initializeSession(this.sessionContext);
           this.destinationManager.initializeSession(this.sessionContext);
           this.previewTicketManager.initializeSession(this.sessionContext);
           this.printTicketManager.initializeSession(this.sessionContext);
diff --git a/ash/webui/print_preview_cros/resources/js/summary_panel_controller.ts b/ash/webui/print_preview_cros/resources/js/summary_panel_controller.ts
index 020d9c1..47614c8c 100644
--- a/ash/webui/print_preview_cros/resources/js/summary_panel_controller.ts
+++ b/ash/webui/print_preview_cros/resources/js/summary_panel_controller.ts
@@ -25,7 +25,7 @@
 export class SummaryPanelController extends EventTarget {
   private sheetsUsed = 0;
   private previewTicketManager = PreviewTicketManager.getInstance();
-  private printTicketManger = PrintTicketManager.getInstance();
+  private printTicketManager = PrintTicketManager.getInstance();
 
   /**
    * @param eventTracker Passed in by owning element to ensure event handlers
@@ -40,10 +40,10 @@
         this.previewTicketManager, PREVIEW_REQUEST_FINISHED_EVENT,
         () => this.onPreviewRequestFinished());
     eventTracker.add(
-        this.printTicketManger, PRINT_REQUEST_STARTED_EVENT,
+        this.printTicketManager, PRINT_REQUEST_STARTED_EVENT,
         (e: Event) => this.onPrintRequestStarted(e));
     eventTracker.add(
-        this.printTicketManger, PRINT_REQUEST_FINISHED_EVENT,
+        this.printTicketManager, PRINT_REQUEST_FINISHED_EVENT,
         (e: Event) => this.onPrintRequestFinished(e));
   }
 
@@ -68,13 +68,13 @@
 
   // Handles behavior for when the print button is clicked.
   handlePrintClicked(): void {
-    this.printTicketManger.sendPrintRequest();
+    this.printTicketManager.sendPrintRequest();
   }
 
   // Handles any required cleanup prior to sending a cancel request to the
   // backend and closing the dialog when the cancel button is clicked.
   handleCancelClicked(): void {
-    this.printTicketManger.cancelPrintRequest();
+    this.printTicketManager.cancelPrintRequest();
   }
 
   // CustomEvent dispatch helper.
@@ -106,7 +106,7 @@
   // Whether the print button should be enabled for the current state.
   shouldDisablePrintButton(): boolean {
     return !this.previewTicketManager.isPreviewLoaded() ||
-        this.printTicketManger.isPrintRequestInProgress();
+        this.printTicketManager.isPrintRequestInProgress();
   }
 }
 
diff --git a/ash/wm/session_state_animator_impl.cc b/ash/wm/session_state_animator_impl.cc
index 52ca974..87acc54 100644
--- a/ash/wm/session_state_animator_impl.cc
+++ b/ash/wm/session_state_animator_impl.cc
@@ -258,24 +258,22 @@
         Shell::GetContainer(root_window, kShellWindowId_ShelfContainer));
   }
   if (container_mask & SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS) {
-    // TODO(antrim): Figure out a way to eliminate a need to exclude shelf
-    // in such way.
-    aura::Window* non_lock_screen_containers = Shell::GetContainer(
-        root_window, kShellWindowId_NonLockScreenContainersContainer);
-    // |non_lock_screen_containers| may already be removed in some tests.
-    constexpr int ContainersToAnimate[] = {
-        kShellWindowId_HomeScreenContainer,
-        kShellWindowId_AlwaysOnTopContainer,
-        kShellWindowId_PipContainer,
-        kShellWindowId_SystemModalContainer,
-    };
-    if (non_lock_screen_containers) {
-      for (aura::Window* window : non_lock_screen_containers->children()) {
-        if ((base::Contains(ContainersToAnimate, window->GetId()) ||
-             desks_util::IsActiveDeskContainer(window))) {
-          containers->push_back(window);
-        }
+    // `non_lock_screen_containers` may already be removed in some tests.
+    if (aura::Window* non_lock_screen_containers = Shell::GetContainer(
+            root_window, kShellWindowId_NonLockScreenContainersContainer);
+        non_lock_screen_containers) {
+      constexpr int ContainersToAnimate[] = {
+          kShellWindowId_HomeScreenContainer,
+          kShellWindowId_AlwaysOnTopContainer,
+          kShellWindowId_FloatContainer,
+          kShellWindowId_PipContainer,
+          kShellWindowId_SystemModalContainer,
+      };
+      for (const int id : ContainersToAnimate) {
+        containers->push_back(Shell::GetContainer(root_window, id));
       }
+      containers->push_back(
+          desks_util::GetActiveDeskContainerForRoot(root_window));
     }
   }
   if (container_mask & SessionStateAnimator::LOCK_SCREEN_WALLPAPER) {
diff --git a/ash/wm/window_util.cc b/ash/wm/window_util.cc
index 8fa3bd1..15a0d79e 100644
--- a/ash/wm/window_util.cc
+++ b/ash/wm/window_util.cc
@@ -79,29 +79,6 @@
 namespace ash::window_util {
 namespace {
 
-// This window targeter reserves space for the portion of the resize handles
-// that extend within a top level window.
-class InteriorResizeHandleTargeterAsh
-    : public chromeos::InteriorResizeHandleTargeter {
- public:
-  InteriorResizeHandleTargeterAsh() = default;
-  InteriorResizeHandleTargeterAsh(const InteriorResizeHandleTargeterAsh&) =
-      delete;
-  InteriorResizeHandleTargeterAsh& operator=(
-      const InteriorResizeHandleTargeterAsh&) = delete;
-  ~InteriorResizeHandleTargeterAsh() override = default;
-
-  bool ShouldUseExtendedBounds(const aura::Window* target) const override {
-    // Fullscreen/maximized windows can't be drag-resized.
-    const WindowState* window_state = WindowState::Get(window());
-    if (window_state && window_state->IsMaximizedOrFullscreenOrPinned())
-      return false;
-
-    // The shrunken hit region only applies to children of |window()|.
-    return InteriorResizeHandleTargeter::ShouldUseExtendedBounds(target);
-  }
-};
-
 // Returns true if `window` has any descendant that is a system modal window or
 // is itself a system modal window.
 bool ContainsSystemModalWindow(const aura::Window* window) {
@@ -403,7 +380,13 @@
 }
 
 void InstallResizeHandleWindowTargeterForWindow(aura::Window* window) {
-  window->SetEventTargeter(std::make_unique<InteriorResizeHandleTargeterAsh>());
+  window->SetEventTargeter(
+      std::make_unique<chromeos::InteriorResizeHandleTargeter>(
+          base::BindRepeating([](const aura::Window* window) {
+            const WindowState* window_state = WindowState::Get(window);
+            return window_state ? window_state->GetStateType()
+                                : chromeos::WindowStateType::kDefault;
+          })));
 }
 
 bool IsDraggingTabs(const aura::Window* window) {
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 665d93d..0fd8801 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -4819,11 +4819,9 @@
       "test/android/javatests/src/org/chromium/base/test/BaseJUnit4TestRule.java",
       "test/android/javatests/src/org/chromium/base/test/LoadNative.java",
       "test/android/javatests/src/org/chromium/base/test/MockitoErrorHandler.java",
-      "test/android/javatests/src/org/chromium/base/test/ResetCachedFlagValuesTestHook.java",
       "test/android/javatests/src/org/chromium/base/test/ScreenshotOnFailureStatement.java",
       "test/android/javatests/src/org/chromium/base/test/TestTraceEvent.java",
       "test/android/javatests/src/org/chromium/base/test/UnitTestLifetimeAssertRule.java",
-      "test/android/javatests/src/org/chromium/base/test/UnitTestNoBrowserProcessHook.java",
       "test/android/javatests/src/org/chromium/base/test/params/BaseJUnit4RunnerDelegate.java",
       "test/android/javatests/src/org/chromium/base/test/params/BlockJUnit4RunnerDelegate.java",
       "test/android/javatests/src/org/chromium/base/test/params/MethodParamAnnotationRule.java",
diff --git a/base/android/java/src/org/chromium/base/Log.java b/base/android/java/src/org/chromium/base/Log.java
index 2d38d8b..27a645d 100644
--- a/base/android/java/src/org/chromium/base/Log.java
+++ b/base/android/java/src/org/chromium/base/Log.java
@@ -64,20 +64,11 @@
         return "cr_" + tag;
     }
 
-    private static boolean isDebug() {
-        // Proguard sets value to false in release builds.
-        return true;
-    }
-
     /**
      * In debug: Forwards to {@link android.util.Log#isLoggable(String, int)}.
      * In release: Always returns false (via proguard rule).
      */
     public static boolean isLoggable(String tag, int level) {
-        // Early return helps optimizer eliminate calls to isLoggable().
-        if (!isDebug() && level <= INFO) {
-            return false;
-        }
         return android.util.Log.isLoggable(tag, level);
     }
 
@@ -92,7 +83,7 @@
      *     one is a {@link Throwable}, its trace will be printed.
      */
     public static void v(String tag, String messageTemplate, Object... args) {
-        if (!isDebug()) return;
+        if (!isLoggable(tag, VERBOSE)) return;
 
         Throwable tr = getThrowableToLog(args);
         String message = formatLog(messageTemplate, tr, args);
@@ -115,7 +106,7 @@
      *     one is a {@link Throwable}, its trace will be printed.
      */
     public static void d(String tag, String messageTemplate, Object... args) {
-        if (!isDebug()) return;
+        if (!isLoggable(tag, DEBUG)) return;
 
         Throwable tr = getThrowableToLog(args);
         String message = formatLog(messageTemplate, tr, args);
diff --git a/base/android/proguard/shared_with_cronet.flags b/base/android/proguard/shared_with_cronet.flags
index 9f27079..3a36042 100644
--- a/base/android/proguard/shared_with_cronet.flags
+++ b/base/android/proguard/shared_with_cronet.flags
@@ -9,14 +9,6 @@
 # Chromium code. They MUST be scoped appropriately to avoid side effects on app
 # code that we do not own.
 
-# Use assumevalues block instead of assumenosideeffects block because Google3
-# proguard cannot parse assumenosideeffects blocks which overwrite return
-# value. Keep this in shared_with_cronet.flags rather than remove_logging.flags
-# so that it's included in cronet.
--assumevalues class org.chromium.base.Log {
-  static boolean isDebug() return false;
-}
-
 # Keep all CREATOR fields within Parcelable that are kept.
 -keepclassmembers class !cr_allowunused,org.chromium.** implements android.os.Parcelable {
   public static *** CREATOR;
diff --git a/base/message_loop/message_pump.cc b/base/message_loop/message_pump.cc
index 692aa86..28e1703e 100644
--- a/base/message_loop/message_pump.cc
+++ b/base/message_loop/message_pump.cc
@@ -121,6 +121,8 @@
   g_explicit_high_resolution_timer_win =
       FeatureList::IsEnabled(kExplicitHighResolutionTimerWin);
   MessagePumpWin::InitializeFeatures();
+#elif BUILDFLAG(IS_ANDROID)
+  MessagePumpAndroid::InitializeFeatures();
 #endif
 }
 
diff --git a/base/message_loop/message_pump_android.cc b/base/message_loop/message_pump_android.cc
index e1ed57ae..e8246037 100644
--- a/base/message_loop/message_pump_android.cc
+++ b/base/message_loop/message_pump_android.cc
@@ -12,6 +12,8 @@
 #include <sys/timerfd.h>
 #include <sys/types.h>
 #include <unistd.h>
+
+#include <atomic>
 #include <utility>
 
 #include "base/android/input_hint_checker.h"
@@ -21,6 +23,7 @@
 #include "base/notreached.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/run_loop.h"
+#include "base/task/task_features.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 
@@ -70,6 +73,8 @@
 // A bit added to the |non_delayed_fd_| to keep it signaled when we yield to
 // native work below.
 constexpr uint64_t kTryNativeWorkBeforeIdleBit = uint64_t(1) << 32;
+
+std::atomic_bool g_fast_to_sleep = false;
 }  // namespace
 
 MessagePumpAndroid::MessagePumpAndroid()
@@ -107,6 +112,10 @@
   close(delayed_fd_);
 }
 
+void MessagePumpAndroid::InitializeFeatures() {
+  g_fast_to_sleep = base::FeatureList::IsEnabled(kPumpFastToSleepAndroid);
+}
+
 void MessagePumpAndroid::OnDelayedLooperCallback() {
   // There may be non-Chromium callbacks on the same ALooper which may have left
   // a pending exception set, and ALooper does not check for this between
@@ -224,26 +233,32 @@
   if (ShouldQuit())
     return;
 
-  // Before declaring this loop idle, yield to native work items and arrange to
-  // be called again (unless we're already in that second call).
-  if (!do_idle_work) {
-    ScheduleWorkInternal(/*do_idle_work=*/true);
-    return;
+  // Under the fast to sleep feature, `do_idle_work` is ignored, and the pump
+  // will always "sleep" after finishing all its work items.
+  if (!g_fast_to_sleep) {
+    // Before declaring this loop idle, yield to native work items and arrange
+    // to be called again (unless we're already in that second call).
+    if (!do_idle_work) {
+      ScheduleWorkInternal(/*do_idle_work=*/true);
+      return;
+    }
+
+    // We yielded to native work items already and they didn't generate a
+    // ScheduleWork() request so we can declare idleness. It's possible for a
+    // ScheduleWork() request to come in racily while this method unwinds, this
+    // is fine and will merely result in it being re-invoked shortly after it
+    // returns.
+    // TODO(scheduler-dev): this doesn't account for tasks that don't ever call
+    // SchedulerWork() but still keep the system non-idle (e.g., the Java
+    // Handler API). It would be better to add an API to query the presence of
+    // native tasks instead of relying on yielding once +
+    // kTryNativeWorkBeforeIdleBit.
+    DCHECK(do_idle_work);
   }
 
-  // We yielded to native work items already and they didn't generate a
-  // ScheduleWork() request so we can declare idleness. It's possible for a
-  // ScheduleWork() request to come in racily while this method unwinds, this is
-  // fine and will merely result in it being re-invoked shortly after it
-  // returns.
-  // TODO(scheduler-dev): this doesn't account for tasks that don't ever call
-  // SchedulerWork() but still keep the system non-idle (e.g., the Java Handler
-  // API). It would be better to add an API to query the presence of native
-  // tasks instead of relying on yielding once + kTryNativeWorkBeforeIdleBit.
-  DCHECK(do_idle_work);
-
-  if (ShouldQuit())
+  if (ShouldQuit()) {
     return;
+  }
 
   // At this point, the java looper might not be idle - it's impossible to know
   // pre-Android-M, so we may end up doing Idle work while java tasks are still
diff --git a/base/message_loop/message_pump_android.h b/base/message_loop/message_pump_android.h
index 3b0d535d..2010492 100644
--- a/base/message_loop/message_pump_android.h
+++ b/base/message_loop/message_pump_android.h
@@ -45,6 +45,8 @@
   void ScheduleDelayedWork(
       const Delegate::NextWorkInfo& next_work_info) override;
 
+  static void InitializeFeatures();
+
   // Attaches |delegate| to this native MessagePump. |delegate| will from then
   // on be invoked by the native loop to process application tasks.
   virtual void Attach(Delegate* delegate);
diff --git a/base/message_loop/message_pump_epoll.cc b/base/message_loop/message_pump_epoll.cc
index 07cf1fbf..0364a2f 100644
--- a/base/message_loop/message_pump_epoll.cc
+++ b/base/message_loop/message_pump_epoll.cc
@@ -379,7 +379,6 @@
 
 void MessagePumpEpoll::HandleWakeUp() {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  BeginNativeWorkBatch();
   processed_io_events_ = true;
   uint64_t value;
   ssize_t n = HANDLE_EINTR(read(wake_event_.get(), &value, sizeof(value)));
diff --git a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
index 59160e01..b8c56cc 100644
--- a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
+++ b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
@@ -182,11 +182,20 @@
   }
 }
 void ThreadControllerWithMessagePumpImpl::BeginNativeWorkBeforeDoWork() {
+  do_work_needed_before_wait_ = true;
+
   if (!g_avoid_schedule_calls_during_native_event_processing.load(
           std::memory_order_relaxed)) {
     return;
   }
-  in_native_work_batch_ = true;
+
+  // Native nested loops don't guarantee that `DoWork()` will be called after
+  // executing native work. This is the invariant that is needed to avoid
+  // calls to `ScheduleWork()`. Since these calls can't be skipped there is
+  // nothing left to do in this function.
+  if (task_execution_allowed_in_native_nested_loop_) {
+    return;
+  }
 
   // Reuse the deduplicator facility to indicate that there is no need for
   // ScheduleWork() until the next time we look for work.
@@ -304,7 +313,7 @@
 void ThreadControllerWithMessagePumpImpl::BeforeWait() {
   // DoWork is guaranteed to be called after native work batches and before
   // wait.
-  CHECK(!in_native_work_batch_);
+  CHECK(!do_work_needed_before_wait_);
 
   // In most cases, DoIdleWork() will already have cleared the
   // `hang_watch_scope_` but in some cases where the native side of the
@@ -320,8 +329,6 @@
 
 MessagePump::Delegate::NextWorkInfo
 ThreadControllerWithMessagePumpImpl::DoWork() {
-  in_native_work_batch_ = false;
-
 #if BUILDFLAG(IS_WIN)
   // We've been already in a wakeup here. Deactivate the high res timer of OS
   // immediately instead of waiting for next DoIdleWork().
@@ -350,6 +357,10 @@
            main_thread_only().yield_to_native_after_batch)) {
     next_work_info.yield_to_native = true;
   }
+
+  // There was just a check for more work.
+  do_work_needed_before_wait_ = false;
+
   // Schedule a continuation.
   WorkDeduplicator::NextTask next_task =
       (next_wake_up && next_wake_up->is_immediate())
@@ -714,6 +725,7 @@
       pump_->ScheduleWork();
     }
   }
+  task_execution_allowed_in_native_nested_loop_ = allowed;
   main_thread_only().task_execution_allowed = allowed;
 }
 
diff --git a/base/task/sequence_manager/thread_controller_with_message_pump_impl.h b/base/task/sequence_manager/thread_controller_with_message_pump_impl.h
index c1992ec..c75657b 100644
--- a/base/task/sequence_manager/thread_controller_with_message_pump_impl.h
+++ b/base/task/sequence_manager/thread_controller_with_message_pump_impl.h
@@ -176,7 +176,8 @@
       GUARDED_BY(task_runner_lock_);
 
   WorkDeduplicator work_deduplicator_;
-  bool in_native_work_batch_ = false;
+  bool do_work_needed_before_wait_ = false;
+  bool task_execution_allowed_in_native_nested_loop_ = false;
 
   ThreadControllerPowerMonitor power_monitor_;
 
diff --git a/base/task/task_features.cc b/base/task/task_features.cc
index 2ec6a7a..3b827c9 100644
--- a/base/task/task_features.cc
+++ b/base/task/task_features.cc
@@ -55,6 +55,10 @@
 
 BASE_FEATURE(kUIPumpImprovementsWin,
              "UIPumpImprovementsWin",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+BASE_FEATURE(kPumpFastToSleepAndroid,
+             "PumpFastToSleepAndroid",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kRunTasksByBatches,
diff --git a/base/task/task_features.h b/base/task/task_features.h
index 8e943e1..bc3c1eea 100644
--- a/base/task/task_features.h
+++ b/base/task/task_features.h
@@ -62,6 +62,11 @@
 // calling Win32 MessagePump functions less often.
 BASE_EXPORT BASE_DECLARE_FEATURE(kUIPumpImprovementsWin);
 
+// Under this feature, the Android pump will call ALooper_PollOnce() rather than
+// unconditionally yielding to native to determine whether there exists native
+// work to be done before sleep.
+BASE_EXPORT BASE_DECLARE_FEATURE(kPumpFastToSleepAndroid);
+
 // Feature to run tasks by batches before pumping out messages.
 BASE_EXPORT BASE_DECLARE_FEATURE(kRunTasksByBatches);
 
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
index 89b34e3..d65b2284 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/BaseJUnit4ClassRunner.java
@@ -22,14 +22,18 @@
 import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.Statement;
 
-import org.chromium.base.CommandLine;
+import org.chromium.base.FeatureParam;
+import org.chromium.base.Flag;
 import org.chromium.base.Log;
 import org.chromium.base.ResettersForTesting;
+import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.test.params.MethodParamAnnotationRule;
 import org.chromium.base.test.util.AndroidSdkLevelSkipCheck;
+import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIfSkipCheck;
 import org.chromium.base.test.util.EspressoIdleTimeoutRule;
+import org.chromium.base.test.util.RequiresRestart;
 import org.chromium.base.test.util.RestrictionSkipCheck;
 import org.chromium.base.test.util.SkipCheck;
 
@@ -162,60 +166,53 @@
     /**
      * See {@link ClassHook}. Prefer to use TestRules over this.
      *
-     * Additional hooks can be added to the list by overriding this method and using {@link
-     * #addToList}:
-     * {@code return addToList(super.getPreClassHooks(), hook1, hook2);}
+     * <p>Additional hooks can be added to the list by overriding this method and using {@link
+     * #addToList}: {@code return addToList(super.getPreClassHooks(), hook1, hook2);}
      */
     @CallSuper
     protected List<ClassHook> getPreClassHooks() {
-        return Arrays.asList(CommandLineFlags.getPreClassHook());
+        return Collections.emptyList();
     }
 
     /**
      * See {@link ClassHook}. Prefer to use TestRules over this.
      *
-     * Additional hooks can be added to the list by overriding this method and using {@link
-     * #addToList}:
-     * {@code return addToList(super.getPostClassHooks(), hook1, hook2);}
+     * <p>Additional hooks can be added to the list by overriding this method and using {@link
+     * #addToList}: {@code return addToList(super.getPostClassHooks(), hook1, hook2);}
      */
     @CallSuper
     protected List<ClassHook> getPostClassHooks() {
-        return Arrays.asList(CommandLineFlags.getPostClassHook());
+        return Collections.emptyList();
     }
 
     /**
      * See {@link TestHook}. Prefer to use TestRules over this.
      *
-     * Additional hooks can be added to the list by overriding this method and using {@link
-     * #addToList}:
-     * {@code return addToList(super.getPreTestHooks(), hook1, hook2);}
+     * <p>Additional hooks can be added to the list by overriding this method and using {@link
+     * #addToList}: {@code return addToList(super.getPreTestHooks(), hook1, hook2);}
      */
     @CallSuper
     protected List<TestHook> getPreTestHooks() {
-        return Arrays.asList(
-                CommandLineFlags.getPreTestHook(),
-                new UnitTestNoBrowserProcessHook(),
-                new ResetCachedFlagValuesTestHook());
+        return Collections.emptyList();
     }
 
     /**
      * See {@link TestHook}. Prefer to use TestRules over this.
      *
-     * Additional hooks can be added to the list by overriding this method and using {@link
-     * #addToList}:
-     * {@code return addToList(super.getPostTestHooks(), hook1, hook2);}
+     * <p>Additional hooks can be added to the list by overriding this method and using {@link
+     * #addToList}: {@code return addToList(super.getPostTestHooks(), hook1, hook2);}
      */
     @CallSuper
     protected List<TestHook> getPostTestHooks() {
-        return Arrays.asList(CommandLineFlags.getPostTestHook());
+        return Collections.emptyList();
     }
 
     /**
-     * Override this method to return a list of method rules that should be applied to all tests
-     * run with this test runner.
+     * Override this method to return a list of method rules that should be applied to all tests run
+     * with this test runner.
      *
-     * Additional rules can be added to the list using {@link #addToList}:
-     * {@code return addToList(super.getDefaultMethodRules(), rule1, rule2);}
+     * <p>Additional rules can be added to the list using {@link #addToList}: {@code return
+     * addToList(super.getDefaultMethodRules(), rule1, rule2);}
      */
     @CallSuper
     protected List<MethodRule> getDefaultMethodRules() {
@@ -269,18 +266,29 @@
         }
 
         ResettersForTesting.beforeClassHooksWillExecute();
-        runPreClassHooks(getDescription().getTestClass());
-        assert CommandLine.isInitialized();
+
+        Class<?> testClass = getDescription().getTestClass();
+        CommandLineFlags.setUpClass(testClass);
+        runPreClassHooks(testClass);
 
         super.run(notifier);
 
         try {
-            runPostClassHooks(getDescription().getTestClass());
+            CommandLineFlags.tearDownClass();
+            runPostClassHooks(testClass);
         } finally {
             ResettersForTesting.afterClassHooksDidExecute();
         }
     }
 
+    private static void blockUnitTestsFromStartingBrowser(FrameworkMethod testMethod) {
+        Batch annotation = testMethod.getDeclaringClass().getAnnotation(Batch.class);
+        if (annotation != null && annotation.value().equals(Batch.UNIT_TESTS)) {
+            if (testMethod.getAnnotation(RequiresRestart.class) != null) return;
+            LibraryLoader.setBrowserProcessStartupBlockedForTesting();
+        }
+    }
+
     @Override
     protected void runChild(FrameworkMethod method, RunNotifier notifier) {
         String testName = method.getName();
@@ -289,11 +297,19 @@
         long start = SystemClock.uptimeMillis();
 
         ResettersForTesting.beforeHooksWillExecute();
+
+        CommandLineFlags.setUpMethod(method.getMethod());
+        blockUnitTestsFromStartingBrowser(method);
+        // TODO(agrieve): These should not reset flag values set in @BeforeClass
+        Flag.resetAllInMemoryCachedValuesForTesting();
+        FeatureParam.resetAllInMemoryCachedValuesForTesting();
+
         runPreTestHooks(method);
 
         super.runChild(method, notifier);
 
         runPostTestHooks(method);
+        CommandLineFlags.tearDownMethod();
         ResettersForTesting.afterHooksDidExecute();
 
         Bundle b = new Bundle();
diff --git a/base/test/android/javatests/src/org/chromium/base/test/ResetCachedFlagValuesTestHook.java b/base/test/android/javatests/src/org/chromium/base/test/ResetCachedFlagValuesTestHook.java
deleted file mode 100644
index 53e86f9..0000000
--- a/base/test/android/javatests/src/org/chromium/base/test/ResetCachedFlagValuesTestHook.java
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2022 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.base.test;
-
-import android.content.Context;
-
-import org.junit.runners.model.FrameworkMethod;
-
-import org.chromium.base.FeatureParam;
-import org.chromium.base.Flag;
-import org.chromium.base.test.BaseJUnit4ClassRunner.TestHook;
-
-/** Resets any cached values held by active {@link Flag} instances. */
-public class ResetCachedFlagValuesTestHook implements TestHook {
-    @Override
-    public void run(Context targetContext, FrameworkMethod testMethod) {
-        Flag.resetAllInMemoryCachedValuesForTesting();
-        FeatureParam.resetAllInMemoryCachedValuesForTesting();
-    }
-}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/UnitTestNoBrowserProcessHook.java b/base/test/android/javatests/src/org/chromium/base/test/UnitTestNoBrowserProcessHook.java
deleted file mode 100644
index d7bdada4..0000000
--- a/base/test/android/javatests/src/org/chromium/base/test/UnitTestNoBrowserProcessHook.java
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2022 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.base.test;
-
-import android.content.Context;
-
-import org.junit.runners.model.FrameworkMethod;
-
-import org.chromium.base.library_loader.LibraryLoader;
-import org.chromium.base.test.BaseJUnit4ClassRunner.TestHook;
-import org.chromium.base.test.util.Batch;
-import org.chromium.base.test.util.RequiresRestart;
-
-/**
- * PreTestHook used to ensure we don't start the browser process in unit tests.
- * */
-public final class UnitTestNoBrowserProcessHook implements TestHook {
-    @Override
-    public void run(Context targetContext, FrameworkMethod testMethod) {
-        Batch annotation = testMethod.getDeclaringClass().getAnnotation(Batch.class);
-        if (annotation != null && annotation.value().equals(Batch.UNIT_TESTS)) {
-            if (testMethod.getAnnotation(RequiresRestart.class) != null) return;
-            LibraryLoader.setBrowserProcessStartupBlockedForTesting();
-        }
-    }
-}
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/ConditionWaiter.java b/base/test/android/javatests/src/org/chromium/base/test/transit/ConditionWaiter.java
index 549d6a16..f066c338 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/ConditionWaiter.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/ConditionWaiter.java
@@ -13,6 +13,7 @@
 import org.chromium.base.TimeUtils;
 import org.chromium.base.test.transit.StatusStore.StatusRegion;
 import org.chromium.base.test.transit.Transition.TransitionOptions;
+import org.chromium.base.test.transit.Transition.Trigger;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.CriteriaNotSatisfiedException;
 
@@ -165,14 +166,15 @@
     private static final String TAG = "Transit";
 
     /**
-     * Blocks waiting for multiple {@link Condition}s, polling them and reporting their status to he
-     * {@link ConditionWait}es.
+     * Start timers, perform the first Condition checks before running the Trigger.
      *
-     * @param conditionWaits the {@link ConditionWait}es to process.
-     * @param options the {@link TransitionOptions} to configure the polling parameters.
-     * @throws AssertionError if not all {@link Condition}s are fulfilled before timing out.
+     * <p>Ensure at least one Condition is not fulfilled before running the Trigger.
+     *
+     * <p>This also makes supplied values available for Conditions that implement Supplier before
+     * {@link Condition#onStartMonitoring()} is called.
      */
-    public static void waitFor(List<ConditionWait> conditionWaits, TransitionOptions options) {
+    static void preCheck(
+            List<ConditionWait> conditionWaits, TransitionOptions options, Trigger trigger) {
         if (conditionWaits.isEmpty()) {
             Log.i(TAG, "No conditions to fulfill.");
         }
@@ -181,6 +183,32 @@
             wait.startTimer();
         }
 
+        boolean anyCriteriaMissing = false;
+        for (ConditionWait wait : conditionWaits) {
+            anyCriteriaMissing |= wait.update();
+        }
+
+        // At least one Condition should be not fulfilled, or this is likely an incorrectly designed
+        // Transition. Exceptions to this rule:
+        //     1. null Trigger, for example when focusing on secondary elements of a screen that
+        //        aren't declared in Station#declareElements().
+        //     2. A explicit exception is made with TransitionOptions.mPossiblyAlreadyFulfilled.
+        //        E.g. when not possible to determine whether the trigger needs to be run.
+        if (!anyCriteriaMissing && !options.mPossiblyAlreadyFulfilled && trigger != null) {
+            throw buildWaitConditionsException(
+                    "All Conditions already fulfilled before running Trigger", conditionWaits);
+        }
+    }
+
+    /**
+     * Blocks waiting for multiple {@link Condition}s, polling them and reporting their status to he
+     * {@link ConditionWait}es.
+     *
+     * @param conditionWaits the {@link ConditionWait}es to process.
+     * @param options the {@link TransitionOptions} to configure the polling parameters.
+     * @throws AssertionError if not all {@link Condition}s are fulfilled before timing out.
+     */
+    static void waitFor(List<ConditionWait> conditionWaits, TransitionOptions options) {
         Runnable checker =
                 () -> {
                     boolean anyCriteriaMissing = false;
@@ -189,7 +217,8 @@
                     }
 
                     if (anyCriteriaMissing) {
-                        throw buildWaitConditionsException(conditionWaits);
+                        throw buildWaitConditionsException(
+                                "Did not meet all conditions", conditionWaits);
                     } else {
                         Log.i(
                                 TAG,
@@ -203,9 +232,9 @@
     }
 
     private static CriteriaNotSatisfiedException buildWaitConditionsException(
-            List<ConditionWait> conditionWaits) {
+            String message, List<ConditionWait> conditionWaits) {
         return new CriteriaNotSatisfiedException(
-                "Did not meet all conditions:\n" + createWaitConditionsSummary(conditionWaits));
+                message + ":\n" + createWaitConditionsSummary(conditionWaits));
     }
 
     private static String createWaitConditionsSummary(List<ConditionWait> conditionStatuses) {
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckIn.java b/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckIn.java
index 2ea0ebacd..2c28bf0 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckIn.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckIn.java
@@ -37,6 +37,7 @@
         // and FacilityCheckOut#exitSync().
         onBeforeTransition();
         mWaits = createWaits();
+        ConditionWaiter.preCheck(mWaits, mOptions, mTrigger);
         for (ConditionWait wait : mWaits) {
             wait.getCondition().onStartMonitoring();
         }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckOut.java b/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckOut.java
index 1d98702..5bdad12 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckOut.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/FacilityCheckOut.java
@@ -38,6 +38,7 @@
         // and FacilityCheckOut#exitSync().
         onBeforeTransition();
         mWaits = createWaits();
+        ConditionWaiter.preCheck(mWaits, mOptions, mTrigger);
         for (ConditionWait wait : mWaits) {
             wait.getCondition().onStartMonitoring();
         }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java b/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java
index b44464b0..b0396fb2 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/Transition.java
@@ -22,7 +22,7 @@
     }
 
     protected final TransitionOptions mOptions;
-    @Nullable private final Trigger mTrigger;
+    @Nullable protected final Trigger mTrigger;
 
     Transition(TransitionOptions options, @Nullable Trigger trigger) {
         mOptions = options;
@@ -91,6 +91,7 @@
         @Nullable List<Condition> mTransitionConditions;
         long mTimeoutMs;
         int mTries = 1;
+        boolean mPossiblyAlreadyFulfilled;
 
         private TransitionOptions() {}
 
@@ -128,6 +129,12 @@
                 mTries = 2;
                 return this;
             }
+
+            /** The Transition's Conditions might already be all fulfilled before the Trigger. */
+            public Builder withPossiblyAlreadyFulfilled() {
+                mPossiblyAlreadyFulfilled = true;
+                return this;
+            }
         }
     }
 }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/Trip.java b/base/test/android/javatests/src/org/chromium/base/test/transit/Trip.java
index fb53d78..5382f88 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/Trip.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/Trip.java
@@ -106,6 +106,7 @@
         mDestination.setStateTransitioningTo();
 
         mWaits = calculateConditionWaits(mOrigin, mDestination, getTransitionConditions());
+        ConditionWaiter.preCheck(mWaits, mOptions, mTrigger);
         for (ConditionWait wait : mWaits) {
             wait.getCondition().onStartMonitoring();
         }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/ViewConditions.java b/base/test/android/javatests/src/org/chromium/base/test/transit/ViewConditions.java
index 4a32e0b..c7d414b 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/ViewConditions.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/ViewConditions.java
@@ -12,7 +12,6 @@
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.any;
 
-import android.content.res.Resources;
 import android.view.View;
 
 import androidx.test.espresso.AmbiguousViewMatcherException;
@@ -21,7 +20,6 @@
 import androidx.test.espresso.UiController;
 import androidx.test.espresso.ViewAction;
 import androidx.test.espresso.ViewInteraction;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.hamcrest.Matcher;
 import org.hamcrest.StringDescription;
@@ -29,18 +27,8 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.test.util.RawFailureHandler;
 
-import java.util.ArrayList;
-import java.util.regex.Pattern;
-
 /** {@link Condition}s related to Android {@link View}s. */
 public class ViewConditions {
-    /** Fulfilled when a single matching View exists and is displayed. */
-    public static class DisplayedCondition extends ExistsCondition {
-        public DisplayedCondition(Matcher<View> matcher, ExistsCondition.Options options) {
-            super(allOf(matcher, isDisplayingAtLeast(ViewElement.MIN_DISPLAYED_PERCENT)), options);
-        }
-    }
-
     /**
      * Fulfilled when a single matching View exists and is displayed, but ignored if |gate| returns
      * true.
@@ -51,7 +39,7 @@
         private final Condition mGate;
 
         public GatedDisplayedCondition(
-                Matcher<View> matcher, Condition gate, ExistsCondition.Options options) {
+                Matcher<View> matcher, Condition gate, DisplayedCondition.Options options) {
             super();
             mDisplayedCondition = new DisplayedCondition(matcher, options);
             mGate = gate;
@@ -81,21 +69,27 @@
         }
     }
 
-    /** Fulfilled when a single matching View exists. */
-    public static class ExistsCondition extends InstrumentationThreadCondition {
+    /** Fulfilled when a single matching View exists and is displayed. */
+    public static class DisplayedCondition extends InstrumentationThreadCondition {
         private final Matcher<View> mMatcher;
         private final Options mOptions;
         private View mViewMatched;
 
-        public ExistsCondition(Matcher<View> matcher, Options options) {
+        private static final String VERBOSE_DESCRIPTION =
+                "(view has effective visibility <VISIBLE> and view.getGlobalVisibleRect() covers at"
+                        + " least <90> percent of the view's area)";
+        private static final String SUCCINCT_DESCRIPTION = "(getGlobalVisibleRect() > 90%)";
+
+        public DisplayedCondition(Matcher<View> matcher, Options options) {
             super();
-            mMatcher = matcher;
+            mMatcher = allOf(matcher, isDisplayingAtLeast(ViewElement.MIN_DISPLAYED_PERCENT));
             mOptions = options;
         }
 
         @Override
         public String buildDescription() {
-            return "View: " + ViewConditions.createMatcherDescription(mMatcher);
+            return "View: "
+                    + createMatcherDescription(mMatcher, VERBOSE_DESCRIPTION, SUCCINCT_DESCRIPTION);
         }
 
         @Override
@@ -162,7 +156,7 @@
             return new Options().new Builder();
         }
 
-        /** Extra options for declaring ExistsCondition. */
+        /** Extra options for declaring DisplayedCondition. */
         public static class Options {
             boolean mExpectEnabled = true;
 
@@ -186,6 +180,11 @@
     public static class NotDisplayedAnymoreCondition extends InstrumentationThreadCondition {
         private final Matcher<View> mMatcher;
 
+        private static final String VERBOSE_DESCRIPTION =
+                "(view has effective visibility <VISIBLE> and view.getGlobalVisibleRect() to return"
+                        + " non-empty rectangle)";
+        private static final String SUCCINCT_DESCRIPTION = "(getGlobalVisibleRect() > 0%)";
+
         public NotDisplayedAnymoreCondition(Matcher<View> matcher) {
             super();
             mMatcher = allOf(matcher, isDisplayed());
@@ -193,7 +192,8 @@
 
         @Override
         public String buildDescription() {
-            return "No more view: " + ViewConditions.createMatcherDescription(mMatcher);
+            return "No more view: "
+                    + createMatcherDescription(mMatcher, VERBOSE_DESCRIPTION, SUCCINCT_DESCRIPTION);
         }
 
         @Override
@@ -213,53 +213,12 @@
         }
     }
 
-    private static String getResourceName(int resId) {
-        return InstrumentationRegistry.getInstrumentation()
-                .getContext()
-                .getResources()
-                .getResourceName(resId);
-    }
-
-    /** Generates a description for the matcher that replaces raw ids with resource names. */
-    private static String createMatcherDescription(Matcher<View> matcher) {
+    /** Returns a less verbose view matcher description. */
+    private static String createMatcherDescription(
+            Matcher<View> matcher, String verboseString, String succinctString) {
         StringDescription d = new StringDescription();
         matcher.describeTo(d);
         String description = d.toString();
-        Pattern numberPattern = Pattern.compile("[0-9]+");
-        java.util.regex.Matcher numberMatcher = numberPattern.matcher(description);
-        ArrayList<Integer> starts = new ArrayList<>();
-        ArrayList<Integer> ends = new ArrayList<>();
-        ArrayList<String> resourceNames = new ArrayList<>();
-        while (numberMatcher.find()) {
-            int resourceId = Integer.parseInt(numberMatcher.group());
-            if (resourceId > 0xFFFFFF) {
-                // Build-time Android resources have ids > 0xFFFFFF
-                starts.add(numberMatcher.start());
-                ends.add(numberMatcher.end());
-                String resourceDescription = createResourceDescription(resourceId);
-                resourceNames.add(resourceDescription);
-            } else {
-                resourceNames.add(numberMatcher.group());
-            }
-        }
-
-        if (starts.size() == 0) return description;
-
-        String newDescription = description.substring(0, starts.get(0));
-        for (int i = 0; i < starts.size(); i++) {
-            newDescription += resourceNames.get(i);
-            int nextStart = (i == starts.size() - 1) ? description.length() : starts.get(i + 1);
-            newDescription += description.substring(ends.get(i), nextStart);
-        }
-
-        return newDescription;
-    }
-
-    private static String createResourceDescription(int possibleResourceId) {
-        try {
-            return getResourceName(possibleResourceId);
-        } catch (Resources.NotFoundException e) {
-            return String.valueOf(possibleResourceId);
-        }
+        return description.replace(verboseString, succinctString);
     }
 }
diff --git a/base/test/android/javatests/src/org/chromium/base/test/transit/ViewElementInState.java b/base/test/android/javatests/src/org/chromium/base/test/transit/ViewElementInState.java
index 5aa9693..02a9192 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/transit/ViewElementInState.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/transit/ViewElementInState.java
@@ -11,7 +11,6 @@
 import org.hamcrest.Matcher;
 
 import org.chromium.base.test.transit.ViewConditions.DisplayedCondition;
-import org.chromium.base.test.transit.ViewConditions.ExistsCondition;
 import org.chromium.base.test.transit.ViewConditions.GatedDisplayedCondition;
 import org.chromium.base.test.transit.ViewConditions.NotDisplayedAnymoreCondition;
 import org.chromium.base.test.transit.ViewElement.Scope;
@@ -40,8 +39,8 @@
         mGate = gate;
 
         Matcher<View> viewMatcher = mViewElement.getViewMatcher();
-        ExistsCondition.Options conditionOptions =
-                ExistsCondition.newOptions()
+        DisplayedCondition.Options conditionOptions =
+                DisplayedCondition.newOptions()
                         .withExpectEnabled(mViewElement.getOptions().mExpectEnabled)
                         .build();
         if (mGate != null) {
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
index dfe6045..e9b93124 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CommandLineFlags.java
@@ -9,17 +9,12 @@
 
 import org.junit.Assert;
 import org.junit.Rule;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
 
 import org.chromium.base.ActivityState;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.CommandLine;
 import org.chromium.base.CommandLineInitUtil;
 import org.chromium.base.Log;
-import org.chromium.base.test.BaseJUnit4ClassRunner.ClassHook;
-import org.chromium.base.test.BaseJUnit4ClassRunner.TestHook;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Inherited;
@@ -286,50 +281,7 @@
         return Arrays.asList(value.split(","));
     }
 
-    private CommandLineFlags() {
-        throw new AssertionError("CommandLineFlags is a non-instantiable class");
-    }
-
-    private static class CommandLineFlagsTestRule implements TestRule {
-        @Override
-        public Statement apply(final Statement base, Description description) {
-            return new Statement() {
-                @Override
-                public void evaluate() throws Throwable {
-                    try {
-                        Class<?> clazz = description.getTestClass();
-                        CommandLineFlags.setUpClass(clazz);
-                        CommandLineFlags.setUpMethod(clazz.getMethod(description.getMethodName()));
-
-                        base.evaluate();
-                    } finally {
-                        CommandLineFlags.tearDownMethod();
-                        CommandLineFlags.tearDownClass();
-                    }
-                }
-            };
-        }
-    }
-
-    public static TestRule getTestRule() {
-        return new CommandLineFlagsTestRule();
-    }
-
-    public static TestHook getPreTestHook() {
-        return (targetContext, testMethod) -> CommandLineFlags.setUpMethod(testMethod.getMethod());
-    }
-
-    public static ClassHook getPreClassHook() {
-        return (targetContext, testClass) -> CommandLineFlags.setUpClass(testClass);
-    }
-
-    public static TestHook getPostTestHook() {
-        return (targetContext, testMethod) -> CommandLineFlags.tearDownMethod();
-    }
-
-    public static ClassHook getPostClassHook() {
-        return (targetContext, testClass) -> CommandLineFlags.tearDownClass();
-    }
+    private CommandLineFlags() {}
 
     public static String getTestCmdLineFile() {
         return "test-cmdline-file";
diff --git a/build/config/sanitizers/BUILD.gn b/build/config/sanitizers/BUILD.gn
index c45cffe..c613687 100644
--- a/build/config/sanitizers/BUILD.gn
+++ b/build/config/sanitizers/BUILD.gn
@@ -14,6 +14,13 @@
   import("//build/config/ios/ios_sdk.gni")
 }
 
+# libfuzzer can't cope with shared objects being unloaded, which sometimes
+# occurs for large fuzzers that involve our graphics stack. Shim out dlclose
+# so that this doesn't occur. At the moment we've only implemented this on Linux
+# since that's the only platform we use these large fuzzers - in the future
+# we could choose to do the same on Windows.
+use_dlcloseshim = use_libfuzzer && is_linux
+
 # Contains the dependencies needed for sanitizers to link into executables and
 # shared_libraries.
 group("deps") {
@@ -63,6 +70,9 @@
     # .o files which depend on them.
     deps += [ "//third_party/fuzztest:centipede_weak_sancov_stubs" ]
   }
+  if (use_dlcloseshim) {
+    deps += [ ":dlclose_shim" ]
+  }
 }
 
 assert(!(is_win && is_asan && current_cpu == "x86"),
@@ -170,6 +180,10 @@
   }
 }
 
+source_set("dlclose_shim") {
+  sources = [ "//build/sanitizers/dlcloseshim.c" ]
+}
+
 # Applies linker flags necessary when either :deps or :default_sanitizer_flags
 # are used.
 config("default_sanitizer_ldflags") {
@@ -269,6 +283,9 @@
       ldflags = [ "/INCREMENTAL:NO" ]
     }
   }
+  if (use_dlcloseshim) {
+    ldflags += [ "-Wl,-wrap,dlclose" ]
+  }
 }
 
 config("common_sanitizer_flags") {
diff --git a/build/fuchsia/test/isolate_daemon.py b/build/fuchsia/test/isolate_daemon.py
index 154819e7..ea39564 100755
--- a/build/fuchsia/test/isolate_daemon.py
+++ b/build/fuchsia/test/isolate_daemon.py
@@ -28,7 +28,9 @@
             return self
 
         def __exit__(self, exc_type, exc_value, traceback):
-            return self._temp_dir.__exit__(exc_type, exc_value, traceback)
+            self._temp_dir.__exit__(exc_type, exc_value, traceback)
+            # Ignore the errors when cleaning up the temporary folder.
+            return True
 
         def name(self):
             """Returns the location of the isolate dir."""
diff --git a/build/sanitizers/dlcloseshim.c b/build/sanitizers/dlcloseshim.c
new file mode 100644
index 0000000..53e2aca5
--- /dev/null
+++ b/build/sanitizers/dlcloseshim.c
@@ -0,0 +1,13 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// In due course we may need to replicate more of the complexity from
+//  base/allocator/partition_allocator/src/partition_alloc/
+//  shim/allocator_shim_internals.h
+// but as we're targeting just libfuzzer Linux builds, perhaps we don't need
+// it.
+
+__attribute__((visibility("default"), noinline)) void __wrap_dlclose(void *handle) {
+  // Do nothing. We don't want to call the real dlclose on libfuzzer builds.
+}
diff --git a/cc/paint/paint_op.h b/cc/paint/paint_op.h
index 0712368..44b1cdd 100644
--- a/cc/paint/paint_op.h
+++ b/cc/paint/paint_op.h
@@ -722,10 +722,7 @@
                               const PaintFlags* flags,
                               SkCanvas* canvas,
                               const PlaybackParams& params);
-  bool IsValid() const {
-    // Reproduce SkRRect::isValid without converting.
-    return flags.IsValid() && oval.isFinite() && oval.isSorted();
-  }
+  bool IsValid() const { return flags.IsValid() && oval.isFinite(); }
   bool EqualsForTesting(const DrawOvalOp& other) const;
   HAS_SERIALIZATION_FUNCTIONS();
 
diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc
index 7aaf705..0567c5d 100644
--- a/cc/paint/paint_op_buffer_unittest.cc
+++ b/cc/paint/paint_op_buffer_unittest.cc
@@ -1587,7 +1587,8 @@
 }
 
 void PushDrawArcOps(PaintOpBuffer* buffer) {
-  size_t len = std::min(test_angles.size() / 2, test_flags.size());
+  size_t len =
+      std::min({test_rects.size(), test_angles.size() / 2, test_flags.size()});
   for (size_t i = 0; i < len; ++i) {
     buffer->push<DrawArcOp>(test_rects[i], test_angles[2 * i],
                             test_angles[2 * i + 1], test_flags[i]);
@@ -1596,7 +1597,8 @@
 }
 
 void PushDrawArcLiteOps(PaintOpBuffer* buffer) {
-  size_t len = std::min(test_angles.size() / 2, test_flags.size());
+  size_t len =
+      std::min({test_rects.size(), test_angles.size() / 2, test_flags.size()});
   for (size_t i = 0; i < len; ++i) {
     if (test_flags[i].CanConvertToCorePaintFlags()) {
       buffer->push<DrawArcLiteOp>(test_rects[i], test_angles[2 * i],
@@ -1611,7 +1613,7 @@
 }
 
 void PushDrawOvalOps(PaintOpBuffer* buffer) {
-  size_t len = std::min(test_paths.size(), test_flags.size());
+  size_t len = std::min(test_rects.size(), test_flags.size());
   for (size_t i = 0; i < len; ++i)
     buffer->push<DrawOvalOp>(test_rects[i], test_flags[i]);
   EXPECT_THAT(*buffer, Each(PaintOpIs<DrawOvalOp>()));
diff --git a/cc/test/paint_op_helper.h b/cc/test/paint_op_helper.h
index 9e9ee26..4564d9e 100644
--- a/cc/test/paint_op_helper.h
+++ b/cc/test/paint_op_helper.h
@@ -142,23 +142,23 @@
         const auto& op = static_cast<const DrawLineLiteOp&>(base_op);
         str << "x0=" << ToString(op.x0) << ", y0=" << ToString(op.y0)
             << ", x1=" << ToString(op.x1) << ", y1=" << ToString(op.y1)
-            << ", flags=" << ToString(op.core_paint_flags) << ")";
+            << ", flags=" << ToString(op.core_paint_flags);
         break;
       }
       case PaintOpType::kDrawArc: {
         const auto& op = static_cast<const DrawArcOp&>(base_op);
-        str << "DrawArcOp(oval=" << ToString(op.oval)
+        str << "oval=" << ToString(op.oval)
             << ", start_angle=" << ToString(op.start_angle_degrees)
             << ", sweep_angle=" << ToString(op.sweep_angle_degrees)
-            << ", flags=" << ToString(op.flags) << ")";
+            << ", flags=" << ToString(op.flags);
         break;
       }
       case PaintOpType::kDrawArcLite: {
         const auto& op = static_cast<const DrawArcLiteOp&>(base_op);
-        str << "DrawArcOp(oval=" << ToString(op.oval)
+        str << "oval=" << ToString(op.oval)
             << ", start_angle=" << ToString(op.start_angle_degrees)
             << ", sweep_angle=" << ToString(op.sweep_angle_degrees)
-            << ", flags=" << ToString(op.core_paint_flags) << ")";
+            << ", flags=" << ToString(op.core_paint_flags);
         break;
       }
       case PaintOpType::kDrawOval: {
diff --git a/chrome/VERSION b/chrome/VERSION
index 0ee0de0..8e0a020 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=127
 MINOR=0
-BUILD=6483
+BUILD=6484
 PATCH=0
diff --git a/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected b/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected
index 55ec3130..a0d43843 100644
--- a/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected
+++ b/chrome/android/expectations/monochrome_64_32_public_bundle.proguard_flags.expected
@@ -960,14 +960,6 @@
 # Chromium code. They MUST be scoped appropriately to avoid side effects on app
 # code that we do not own.
 
-# Use assumevalues block instead of assumenosideeffects block because Google3
-# proguard cannot parse assumenosideeffects blocks which overwrite return
-# value. Keep this in shared_with_cronet.flags rather than remove_logging.flags
-# so that it's included in cronet.
--assumevalues class org.chromium.base.Log {
-  static boolean isDebug() return false;
-}
-
 # Keep all CREATOR fields within Parcelable that are kept.
 -keepclassmembers class !cr_allowunused,org.chromium.** implements android.os.Parcelable {
   public static *** CREATOR;
diff --git a/chrome/android/features/start_surface/java/res/values/dimens.xml b/chrome/android/features/start_surface/java/res/values/dimens.xml
index 38d0539..0a031816 100644
--- a/chrome/android/features/start_surface/java/res/values/dimens.xml
+++ b/chrome/android/features/start_surface/java/res/values/dimens.xml
@@ -5,7 +5,6 @@
 found in the LICENSE file.
 -->
 <resources xmlns:tools="http://schemas.android.com/tools">
-    <dimen name="tasks_surface_body_top_margin">24dp</dimen>
     <dimen name="mv_tiles_container_top_margin">17dp</dimen>
 
     <!-- Surface Polish -->
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceHomeLayout.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceHomeLayout.java
index bf11faf..1f98557 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceHomeLayout.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceHomeLayout.java
@@ -15,8 +15,6 @@
 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
 import org.chromium.chrome.browser.compositor.scene_layer.SolidColorSceneLayer;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
-import org.chromium.chrome.browser.hub.HubFieldTrial;
 import org.chromium.chrome.browser.layouts.EventFilter;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.layouts.scene_layer.SceneLayer;
@@ -24,7 +22,6 @@
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.features.tasks.TasksView;
 import org.chromium.components.browser_ui.styles.ChromeColors;
-import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 
 /** A {@link Layout} that shows Start Surface home view. */
 public class StartSurfaceHomeLayout extends Layout {
@@ -172,24 +169,15 @@
     private void ensureSceneLayerCreated() {
         if (mSceneLayer != null) return;
 
-        boolean isSurfacePolishEnabled = ChromeFeatureList.sSurfacePolish.isEnabled();
-        if (isSurfacePolishEnabled || HubFieldTrial.isHubEnabled()) {
-            SolidColorSceneLayer sceneLayer = new SolidColorSceneLayer();
-            Context context = getContext();
-            @ColorInt int color;
-            if (isSurfacePolishEnabled) {
-                color =
-                        ChromeColors.getSurfaceColor(
-                                context, R.dimen.home_surface_background_color_elevation);
-            } else {
-                color = SemanticColorUtils.getDefaultBgColor(context);
-            }
-            sceneLayer.setBackgroundColor(color);
+        SolidColorSceneLayer sceneLayer = new SolidColorSceneLayer();
+        Context context = getContext();
+        @ColorInt int color;
+        color =
+                ChromeColors.getSurfaceColor(
+                        context, R.dimen.home_surface_background_color_elevation);
+        sceneLayer.setBackgroundColor(color);
 
-            mSceneLayer = sceneLayer;
-        } else {
-            mSceneLayer = new SceneLayer();
-        }
+        mSceneLayer = sceneLayer;
     }
 
     private void onTabSelecting(int tabId) {
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
index cd8e2d3..466961f 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -26,7 +26,6 @@
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_CONTAINER_TOP_MARGIN;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.RESET_TASK_SURFACE_HEADER_SCROLL_POSITION;
-import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.TASKS_SURFACE_BODY_TOP_MARGIN;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.TOP_TOOLBAR_PLACEHOLDER_HEIGHT;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.VOICE_SEARCH_BUTTON_CLICK_LISTENER;
 
@@ -1210,10 +1209,6 @@
         if (mIsSurfacePolishEnabled) return;
 
         Resources resources = mContext.getResources();
-        mPropertyModel.set(
-                TASKS_SURFACE_BODY_TOP_MARGIN,
-                resources.getDimensionPixelSize(R.dimen.tasks_surface_body_top_margin));
-
         // TODO(crbug.com/40221888): Clean up this code when the refactor is enabled.
         mPropertyModel.set(
                 MV_TILES_CONTAINER_TOP_MARGIN,
@@ -1341,13 +1336,13 @@
     }
 
     /**
-     * Update the background color of the start surface based on whether it is polished or not ,
-     * in the incognito mode or non-incognito mode.
+     * Update the background color of the start surface based on whether it is polished or not , in
+     * the incognito mode or non-incognito mode.
      */
     @VisibleForTesting
     void updateBackgroundColor(PropertyModel propertyModel) {
         @ColorInt int surfaceBackgroundColor;
-        if (mIsSurfacePolishEnabled && !mIsIncognito) {
+        if (!mIsIncognito) {
             surfaceBackgroundColor =
                     ChromeColors.getSurfaceColor(
                             mContext, R.dimen.home_surface_background_color_elevation);
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksSurfaceProperties.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksSurfaceProperties.java
index 1dd03fd9..b11b729 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksSurfaceProperties.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksSurfaceProperties.java
@@ -64,8 +64,6 @@
             new PropertyModel.WritableBooleanPropertyKey();
     public static final PropertyModel.WritableObjectPropertyKey<View.OnClickListener>
             VOICE_SEARCH_BUTTON_CLICK_LISTENER = new PropertyModel.WritableObjectPropertyKey<>();
-    public static final PropertyModel.WritableIntPropertyKey TASKS_SURFACE_BODY_TOP_MARGIN =
-            new PropertyModel.WritableIntPropertyKey();
     public static final PropertyModel.WritableIntPropertyKey MV_TILES_CONTAINER_TOP_MARGIN =
             new PropertyModel.WritableIntPropertyKey();
     public static final PropertyModel.WritableIntPropertyKey TOP_TOOLBAR_PLACEHOLDER_HEIGHT =
@@ -99,7 +97,6 @@
                 QUERY_TILES_VISIBLE,
                 MAGIC_STACK_VISIBLE,
                 VOICE_SEARCH_BUTTON_CLICK_LISTENER,
-                TASKS_SURFACE_BODY_TOP_MARGIN,
                 MV_TILES_CONTAINER_TOP_MARGIN,
                 RESET_TASK_SURFACE_HEADER_SCROLL_POSITION,
                 TOP_TOOLBAR_PLACEHOLDER_HEIGHT,
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksView.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksView.java
index 48dc64d..e4f7bb13 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksView.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksView.java
@@ -308,16 +308,8 @@
     }
 
     /**
-     * Set the top margin for the tasks surface body.
-     * @param topMargin The top margin to set.
-     */
-    void setTasksSurfaceBodyTopMargin(int topMargin) {
-        MarginLayoutParams params = (MarginLayoutParams) getBodyViewContainer().getLayoutParams();
-        params.topMargin = topMargin;
-    }
-
-    /**
      * Set the top margin for the mv tiles container.
+     *
      * @param topMargin The top margin to set.
      */
     void setMVTilesContainerTopMargin(int topMargin) {
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksViewBinder.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksViewBinder.java
index 035c702..5026985 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksViewBinder.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/tasks/TasksViewBinder.java
@@ -27,7 +27,6 @@
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.QUERY_TILES_VISIBLE;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.RESET_TASK_SURFACE_HEADER_SCROLL_POSITION;
-import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.TASKS_SURFACE_BODY_TOP_MARGIN;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.TOP_TOOLBAR_PLACEHOLDER_HEIGHT;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.VOICE_SEARCH_BUTTON_CLICK_LISTENER;
 
@@ -99,8 +98,6 @@
             view.getSearchBoxCoordinator()
                     .addVoiceSearchButtonClickListener(
                             model.get(VOICE_SEARCH_BUTTON_CLICK_LISTENER));
-        } else if (propertyKey == TASKS_SURFACE_BODY_TOP_MARGIN) {
-            view.setTasksSurfaceBodyTopMargin(model.get(TASKS_SURFACE_BODY_TOP_MARGIN));
         } else if (propertyKey == MV_TILES_CONTAINER_TOP_MARGIN) {
             view.setMVTilesContainerTopMargin(model.get(MV_TILES_CONTAINER_TOP_MARGIN));
         } else if (propertyKey == RESET_TASK_SURFACE_HEADER_SCROLL_POSITION) {
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java
index c26b6cf..1606692f 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTabSwitcherTest.java
@@ -62,7 +62,6 @@
 import org.chromium.chrome.browser.layouts.LayoutTestUtils;
 import org.chromium.chrome.browser.layouts.LayoutType;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tasks.pseudotab.TabAttributeCache;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
@@ -210,8 +209,6 @@
         TabUiTestHelper.finishActivity(mActivityTestRule.getActivity());
         StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile(0, mBrowserControlsStateProvider);
         StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile(1, mBrowserControlsStateProvider);
-        TabAttributeCache.setRootIdForTesting(0, 0);
-        TabAttributeCache.setRootIdForTesting(1, 0);
         StartSurfaceTestUtils.createTabStatesAndMetadataFile(new int[] {0, 1});
 
         // Restart and open tab grid dialog.
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
index 58a7617..99862bc 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java
@@ -71,8 +71,6 @@
 import org.chromium.chrome.browser.tabpersistence.TabStateDirectory;
 import org.chromium.chrome.browser.tabpersistence.TabStateFileManager;
 import org.chromium.chrome.browser.tasks.ReturnToChromeUtil;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
-import org.chromium.chrome.browser.tasks.pseudotab.TabAttributeCache;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
 import org.chromium.chrome.browser.toolbar.top.ToolbarPhone;
 import org.chromium.chrome.test.ChromeActivityTestRule;
@@ -225,7 +223,6 @@
             // Creating tabs and restart the activity would do the trick, but we cannot do that for
             // Instant start because we cannot unload native library.
             // Create fake TabState files to emulate having one tab in previous session.
-            TabAttributeCache.setTitleForTesting(0, "tab title");
             startMainActivityFromLauncher(activityTestRule);
         } else {
             assertFalse(ReturnToChromeUtil.shouldShowTabSwitcher(-1, false));
@@ -355,23 +352,40 @@
      * @param tabIds all the Tab IDs in the normal tab model.
      */
     public static void createTabStatesAndMetadataFile(int[] tabIds) throws IOException {
-        createTabStatesAndMetadataFile(tabIds, null, 0);
+        createTabStatesAndMetadataFile(tabIds, null, null, 0);
     }
 
     /**
      * Create all the files so that tab models can be restored.
      *
      * @param tabIds all the Tab IDs in the normal tab model.
+     * @param rootIds all the root IDs in the normal tab model.
+     */
+    public static void createTabStatesAndMetadataFile(int[] tabIds, @Nullable int[] rootIds)
+            throws IOException {
+        createTabStatesAndMetadataFile(tabIds, rootIds, null, 0);
+    }
+
+    /**
+     * Create all the files so that tab models can be restored.
+     *
+     * @param tabIds all the Tab IDs in the normal tab model.
+     * @param rootIds all the root IDs in the normal tab model.
      * @param urls all of the URLs in the normal tab model.
      * @param selectedIndex the selected index of normal tab model.
      */
     public static void createTabStatesAndMetadataFile(
-            int[] tabIds, @Nullable String[] urls, int selectedIndex) throws IOException {
-        createTabStatesAndMetadataFile(tabIds, urls, selectedIndex, true);
+            int[] tabIds, @Nullable int[] rootIds, @Nullable String[] urls, int selectedIndex)
+            throws IOException {
+        createTabStatesAndMetadataFile(tabIds, rootIds, urls, selectedIndex, true);
     }
 
     private static void createTabStatesAndMetadataFile(
-            int[] tabIds, @Nullable String[] urls, int selectedIndex, boolean createStateFile)
+            int[] tabIds,
+            int[] rootIds,
+            @Nullable String[] urls,
+            int selectedIndex,
+            boolean createStateFile)
             throws IOException {
         TabPersistentStore.TabModelMetadata normalInfo =
                 new TabPersistentStore.TabModelMetadata(selectedIndex);
@@ -381,7 +395,8 @@
             normalInfo.urls.add(url);
 
             if (createStateFile) {
-                saveTabState(tabIds[i], false);
+                int rootId = rootIds == null ? tabIds[i] : rootIds[i];
+                saveTabState(tabIds[i], rootId, false);
             }
         }
         TabPersistentStore.TabModelMetadata incognitoInfo =
@@ -407,11 +422,12 @@
      */
     public static void prepareTabStateMetadataFile(
             int[] tabIds, @Nullable String[] urls, int selectedIndex) throws IOException {
-        createTabStatesAndMetadataFile(tabIds, urls, selectedIndex, false);
+        createTabStatesAndMetadataFile(tabIds, null, urls, selectedIndex, false);
     }
 
     /**
      * Create thumbnail bitmap of the tab based on the given id and write it to file.
+     *
      * @param tabId The id of the target tab.
      * @param browserControlsStateProvider For getting the top offset.
      * @return The bitmap created.
@@ -690,10 +706,12 @@
 
     /**
      * Create a file so that a TabState can be restored later.
+     *
      * @param tabId the Tab ID
+     * @param tabId the Root ID
      * @param encrypted for Incognito mode
      */
-    private static void saveTabState(int tabId, boolean encrypted) {
+    private static void saveTabState(int tabId, int rootId, boolean encrypted) {
         File file =
                 TabStateFileManager.getTabStateFile(
                         TabStateDirectory.getOrCreateTabbedModeStateDirectory(),
@@ -703,7 +721,7 @@
         writeFile(file, M26_GOOGLE_COM.encodedTabState);
 
         TabState tabState = TabStateFileManager.restoreTabStateInternal(file, false);
-        tabState.rootId = PseudoTab.fromTabId(tabId).getRootId();
+        tabState.rootId = rootId;
         TabStateFileManager.saveStateInternal(file, tabState, encrypted);
     }
 
diff --git a/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java b/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
index 120bb1e..72d3685 100644
--- a/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
+++ b/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
@@ -44,7 +44,6 @@
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_CONTAINER_TOP_MARGIN;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
-import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.TASKS_SURFACE_BODY_TOP_MARGIN;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.VOICE_SEARCH_BUTTON_CLICK_LISTENER;
 
 import android.app.Activity;
@@ -812,14 +811,8 @@
 
     @Test
     public void initializeStartSurfaceTopMargins() {
-        int tasksSurfaceBodyTopMargin = 0;
-
         createStartSurfaceMediatorWithoutInit(
-
                 /* hadWarmStart= */ false, /* useMagicStack= */ false);
-        assertThat(
-                mPropertyModel.get(TASKS_SURFACE_BODY_TOP_MARGIN),
-                equalTo(tasksSurfaceBodyTopMargin));
         assertThat(mPropertyModel.get(MV_TILES_CONTAINER_TOP_MARGIN), equalTo(0));
     }
 
diff --git a/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/tasks/TasksViewBinderUnitTest.java b/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/tasks/TasksViewBinderUnitTest.java
index 30e5eb2..7c661cfe 100644
--- a/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/tasks/TasksViewBinderUnitTest.java
+++ b/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/tasks/TasksViewBinderUnitTest.java
@@ -25,7 +25,6 @@
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MAGIC_STACK_VISIBLE;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_CONTAINER_TOP_MARGIN;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
-import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.TASKS_SURFACE_BODY_TOP_MARGIN;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.TOP_TOOLBAR_PLACEHOLDER_HEIGHT;
 import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.VOICE_SEARCH_BUTTON_CLICK_LISTENER;
 
@@ -292,18 +291,6 @@
 
     @Test
     @SmallTest
-    public void testSetTasksSurfaceBodyTopMargin() {
-        ViewGroup.MarginLayoutParams params =
-                (ViewGroup.MarginLayoutParams) mTasksView.getBodyViewContainer().getLayoutParams();
-        assertEquals(0, params.topMargin);
-
-        mTasksViewPropertyModel.set(TASKS_SURFACE_BODY_TOP_MARGIN, 16);
-
-        assertEquals(16, params.topMargin);
-    }
-
-    @Test
-    @SmallTest
     public void testSetMVTilesContainerTopMargin() {
         ViewGroup.MarginLayoutParams params =
                 (ViewGroup.MarginLayoutParams)
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java
deleted file mode 100644
index 795a888..0000000
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java
+++ /dev/null
@@ -1,324 +0,0 @@
-// Copyright 2019 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.tasks.pseudotab;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.chromium.base.Token;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tabmodel.TabList;
-import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
-import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.url.GURL;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.concurrent.GuardedBy;
-
-/** Representation of a Tab-like card in the Grid Tab Switcher. */
-public class PseudoTab {
-    private static final String TAG = "PseudoTab";
-
-    private final Integer mTabId;
-    private final WeakReference<Tab> mTab;
-
-    @GuardedBy("sLock")
-    private static final Map<Integer, PseudoTab> sAllTabs = new LinkedHashMap<>();
-
-    private static final Object sLock = new Object();
-
-    /** An interface to get the title to be used for a tab. */
-    public interface TitleProvider {
-        String getTitle(Context context, PseudoTab tab);
-    }
-
-    /** Construct from a tab ID. An earlier instance with the same ID can be returned. */
-    public static PseudoTab fromTabId(int tabId) {
-        synchronized (sLock) {
-            PseudoTab cached = sAllTabs.get(tabId);
-            if (cached != null) return cached;
-            return new PseudoTab(tabId);
-        }
-    }
-
-    private PseudoTab(int tabId) {
-        mTabId = tabId;
-        mTab = null;
-        sAllTabs.put(getId(), this);
-    }
-
-    /**
-     * Construct from a {@link Tab}. An earlier instance with the same {@link Tab} can be returned.
-     */
-    public static PseudoTab fromTab(@NonNull Tab tab) {
-        synchronized (sLock) {
-            PseudoTab cached = sAllTabs.get(tab.getId());
-            if (cached != null && cached.hasRealTab()) {
-                if (cached.getTab() == tab) {
-                    return cached;
-                } else {
-                    assert tab.getWebContents() == null
-                            || cached.getTab().getWebContents() == null
-                            || cached.getTab().getWebContents().getTopLevelNativeWindow() == null;
-                    return new PseudoTab(tab);
-                }
-            }
-            // We need to upgrade a pre-native Tab to a post-native Tab.
-            return new PseudoTab(tab);
-        }
-    }
-
-    private PseudoTab(@NonNull Tab tab) {
-        mTabId = tab.getId();
-        mTab = new WeakReference<>(tab);
-        sAllTabs.put(getId(), this);
-    }
-
-    /**
-     * Convert a list of {@link Tab} to a list of {@link PseudoTab}.
-     * @param tabs A list of {@link Tab}
-     * @return A list of {@link PseudoTab}
-     */
-    public static List<PseudoTab> getListOfPseudoTab(@Nullable List<Tab> tabs) {
-        List<PseudoTab> pseudoTabs = null;
-        if (tabs != null) {
-            pseudoTabs = new ArrayList<>();
-            for (Tab tab : tabs) {
-                pseudoTabs.add(fromTab(tab));
-            }
-        }
-        return pseudoTabs;
-    }
-
-    /**
-     * Convert a {@link TabList} to a list of {@link PseudoTab}.
-     * @param tabList A {@link TabList}
-     * @return A list of {@link PseudoTab}
-     */
-    public static List<PseudoTab> getListOfPseudoTab(@Nullable TabList tabList) {
-        List<PseudoTab> pseudoTabs = null;
-        if (tabList != null) {
-            pseudoTabs = new ArrayList<>();
-            for (int i = 0; i < tabList.getCount(); i++) {
-                Tab tab = tabList.getTabAt(i);
-                if (tab != null) {
-                    pseudoTabs.add(fromTab(tab));
-                }
-            }
-        }
-        return pseudoTabs;
-    }
-
-    @Override
-    public String toString() {
-        assert mTabId != null;
-        return "Tab " + mTabId;
-    }
-
-    /**
-     * @return The ID of the {@link PseudoTab}
-     */
-    public int getId() {
-        return mTabId;
-    }
-
-    /**
-     * Get the title of the {@link PseudoTab} through a {@link TitleProvider}.
-     *
-     * If the {@link TitleProvider} is {@code null}, fall back to {@link #getTitle()}.
-     *
-     * @param context The activity context.
-     * @param titleProvider The {@link TitleProvider} to provide the title.
-     * @return The title
-     */
-    public String getTitle(Context context, @Nullable TitleProvider titleProvider) {
-        if (titleProvider != null) return titleProvider.getTitle(context, this);
-        return getTitle();
-    }
-
-    /**
-     * Get the title of the {@link PseudoTab}.
-     * @return The title
-     */
-    public String getTitle() {
-        if (mTab != null && mTab.get() != null && mTab.get().isInitialized()) {
-            return mTab.get().getTitle();
-        }
-        assert mTabId != null;
-        return TabAttributeCache.getTitle(mTabId);
-    }
-
-    /**
-     * Get the URL of the {@link PseudoTab}.
-     * @return The URL
-     */
-    public GURL getUrl() {
-        if (mTab != null && mTab.get() != null && mTab.get().isInitialized()) {
-            return mTab.get().getUrl();
-        }
-        assert mTabId != null;
-        return TabAttributeCache.getUrl(mTabId);
-    }
-
-    /** Whether the tab is closing or destroyed. */
-    public boolean isClosingOrDestroyed() {
-        // The tab is not backed by a real tab, assume it is alive.
-        if (mTab == null) return false;
-
-        // The tab is backed by a real tab check if it still exists and its state.
-        Tab tab = mTab.get();
-        return tab == null || tab.isClosing() || tab.isDestroyed();
-    }
-
-    /**
-     * Get the root ID of the {@link PseudoTab}.
-     * @return The root ID
-     */
-    public int getRootId() {
-        if (mTab != null && mTab.get() != null && mTab.get().isInitialized()) {
-            return mTab.get().getRootId();
-        }
-        assert mTabId != null;
-        return TabAttributeCache.getRootId(mTabId);
-    }
-
-    /**
-     * Get the tab group ID of the {@link PseudoTab}.
-     *
-     * @return The tab group ID
-     */
-    public @Nullable Token getTabGroupId() {
-        if (mTab != null && mTab.get() != null && mTab.get().isInitialized()) {
-            return mTab.get().getTabGroupId();
-        }
-        assert mTabId != null;
-        return TabAttributeCache.getTabGroupId(mTabId);
-    }
-
-    /**
-     * @return The timestamp of the {@link PseudoTab}.
-     */
-    public long getTimestampMillis() {
-        if (mTab != null && mTab.get() != null && mTab.get().isInitialized()) {
-            return mTab.get().getTimestampMillis();
-        }
-        assert mTabId != null;
-        return TabAttributeCache.getTimestampMillis(mTabId);
-    }
-
-    /**
-     * @return Whether the {@link PseudoTab} is in the Incognito mode.
-     */
-    public boolean isIncognito() {
-        if (mTab != null && mTab.get() != null) return mTab.get().isIncognito();
-        assert mTabId != null;
-        return false;
-    }
-
-    /**
-     * @return Whether an underlying real {@link Tab} is available.
-     */
-    public boolean hasRealTab() {
-        return getTab() != null;
-    }
-
-    /**
-     * Get the underlying real {@link Tab}. We should avoid using this.
-     * @return The underlying real {@link Tab}.
-     */
-    @Deprecated
-    public @Nullable Tab getTab() {
-        if (mTab != null) return mTab.get();
-        return null;
-    }
-
-    /**
-     * Clear the internal static storage as if the app is restarted. This should/can be called when
-     * emulating restarting in instrumented tests, or between Robolectric tests.
-     */
-    public static void clearForTesting() {
-        synchronized (sLock) {
-            sAllTabs.clear();
-        }
-    }
-
-    /**
-     * Get related tabs of a certain {@link PseudoTab}, through {@link TabModelFilter}s if
-     * available.
-     *
-     * @param context The activity context.
-     * @param member The {@link PseudoTab} related to
-     * @param tabModelSelector The {@link TabModelSelector} to query the tab relation
-     * @return Related {@link PseudoTab}s
-     */
-    public static @NonNull List<PseudoTab> getRelatedTabs(
-            Context context, PseudoTab member, @NonNull TabModelSelector tabModelSelector) {
-        synchronized (sLock) {
-            List<Tab> relatedTabs = getRelatedTabList(tabModelSelector, member.getId());
-            if (relatedTabs != null) return getListOfPseudoTab(relatedTabs);
-
-            List<PseudoTab> related = new ArrayList<>();
-            int rootId = member.getRootId();
-            if (rootId == Tab.INVALID_TAB_ID) {
-                related.add(member);
-                return related;
-            }
-            for (Integer key : sAllTabs.keySet()) {
-                PseudoTab tab = sAllTabs.get(key);
-                assert tab != null;
-                if (tab.getRootId() == Tab.INVALID_TAB_ID) continue;
-                if (tab.getRootId() != rootId) continue;
-                related.add(tab);
-            }
-            assert related.size() > 0;
-            return related;
-        }
-    }
-
-    /**
-     * Get related tabs of a certain {@link PseudoTab}, through {@link TabModelFilter}s if
-     * available.
-     *
-     * @param context The activity context.
-     * @param member The {@link PseudoTab} related to
-     * @param tabModelFilter The {@link TabModelFilter} to query the tab relation
-     * @return Related {@link PseudoTab}s
-     */
-    public static @NonNull List<PseudoTab> getRelatedTabs(
-            Context context, PseudoTab member, @NonNull TabModelFilter filter) {
-        assert filter.isTabModelRestored() : "Trying to get related tabs for uninitialized filter.";
-        synchronized (sLock) {
-            List<Tab> relatedTabs = filter.getRelatedTabList(member.getId());
-            return getListOfPseudoTab(relatedTabs);
-        }
-    }
-
-    private static @Nullable List<Tab> getRelatedTabList(
-            @NonNull TabModelSelector tabModelSelector, int tabId) {
-        if (!tabModelSelector.isTabStateInitialized()) {
-            throw new IllegalStateException();
-        }
-        TabModelFilterProvider provider = tabModelSelector.getTabModelFilterProvider();
-        List<Tab> related = provider.getTabModelFilter(false).getRelatedTabList(tabId);
-        if (related.size() > 0) return related;
-        related = provider.getTabModelFilter(true).getRelatedTabList(tabId);
-        assert related.size() > 0;
-        return related;
-    }
-
-    static int getAllTabsCountForTests() {
-        synchronized (sLock) {
-            return sAllTabs.size();
-        }
-    }
-}
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java
deleted file mode 100644
index e8891bd6f..0000000
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java
+++ /dev/null
@@ -1,442 +0,0 @@
-// Copyright 2019 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.tasks.pseudotab;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.text.TextUtils;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.ResettersForTesting;
-import org.chromium.base.Token;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.chrome.browser.tabmodel.TabModelObserver;
-import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
-import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
-import org.chromium.components.search_engines.TemplateUrlService;
-import org.chromium.content_public.browser.NavigationController;
-import org.chromium.content_public.browser.NavigationHandle;
-import org.chromium.content_public.browser.NavigationHistory;
-import org.chromium.url.GURL;
-
-import java.util.Objects;
-
-/** Cache for attributes of {@link PseudoTab} to be available before native is ready. */
-public class TabAttributeCache {
-    private static final String PREFERENCES_NAME = "tab_attribute_cache";
-    private static final long NO_TAB_GROUP_ID = 0L;
-
-    private static SharedPreferences sPref;
-    private final TabModelSelector mTabModelSelector;
-    private final TabModelObserver mTabModelObserver;
-    private final TabModelSelectorTabObserver mTabModelSelectorTabObserver;
-    private final TabModelSelectorObserver mTabModelSelectorObserver;
-
-    interface LastSearchTermProvider {
-        String getLastSearchTerm(Tab tab);
-    }
-
-    private static LastSearchTermProvider sLastSearchTermProviderForTesting;
-
-    private static SharedPreferences getSharedPreferences() {
-        if (sPref == null) {
-            sPref =
-                    ContextUtils.getApplicationContext()
-                            .getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
-            ResettersForTesting.register(() -> sPref = null);
-        }
-        return sPref;
-    }
-
-    /**
-     * Create a TabAttributeCache instance to observe tab attribute changes.
-     *
-     * Note that querying tab attributes doesn't rely on having an instance.
-     * @param tabModelSelector The {@link TabModelSelector} to observe.
-     */
-    public TabAttributeCache(TabModelSelector tabModelSelector) {
-        // TODO(hanxi): makes TabAttributeCache a singleton. The TabAttributeCache should be
-        //  instantiated and exactly once before it is used.
-        mTabModelSelector = tabModelSelector;
-        mTabModelSelectorTabObserver =
-                new TabModelSelectorTabObserver(mTabModelSelector) {
-                    @Override
-                    public void onUrlUpdated(Tab tab) {
-                        if (tab.isIncognito()) return;
-                        cacheUrl(tab.getId(), tab.getUrl());
-                    }
-
-                    @Override
-                    public void onTitleUpdated(Tab tab) {
-                        if (tab.isIncognito()) return;
-                        String title = tab.getTitle();
-                        cacheTitle(tab.getId(), title);
-                    }
-
-                    @Override
-                    public void onRootIdChanged(Tab tab, int newRootId) {
-                        if (tab.isIncognito()) return;
-                        assert newRootId == tab.getRootId();
-                        cacheRootId(tab.getId(), newRootId);
-                    }
-
-                    @Override
-                    public void onTabGroupIdChanged(Tab tab, @Nullable Token tabGroupId) {
-                        if (tab.isIncognito()) return;
-                        assert Objects.equals(tabGroupId, tab.getTabGroupId());
-                        cacheTabGroupId(tab.getId(), tabGroupId);
-                    }
-
-                    @Override
-                    public void onTimestampChanged(Tab tab, long timestampMillis) {
-                        if (tab.isIncognito()) return;
-                        assert timestampMillis == tab.getTimestampMillis();
-                        cacheTimestampMillis(tab.getId(), timestampMillis);
-                    }
-
-                    @Override
-                    public void onDidFinishNavigationInPrimaryMainFrame(
-                            Tab tab, NavigationHandle navigationHandle) {
-                        if (tab.isIncognito()) return;
-                        if (tab.getWebContents() == null) return;
-                        // TODO(crbug.com/40117193): skip cacheLastSearchTerm() according to
-                        //  isValidSearchFormUrl() and PageTransition.GENERATED for optimization.
-                        cacheLastSearchTerm(tab);
-                    }
-                };
-
-        mTabModelObserver =
-                new TabModelObserver() {
-                    @Override
-                    public void tabClosureCommitted(Tab tab) {
-                        int id = tab.getId();
-                        getSharedPreferences()
-                                .edit()
-                                .remove(getDeprecatedUrlKey(id))
-                                .remove(getUrlKey(id))
-                                .remove(getTitleKey(id))
-                                .remove(getRootIdKey(id))
-                                .remove(getTabGroupIdHighKey(id))
-                                .remove(getTabGroupIdLowKey(id))
-                                .remove(getTimestampMillisKey(id))
-                                .remove(getLastSearchTermKey(id))
-                                .apply();
-                    }
-                };
-
-        mTabModelSelectorObserver =
-                new TabModelSelectorObserver() {
-                    @Override
-                    public void onTabStateInitialized() {
-                        // TODO(wychen): after this cache is enabled by default, we only need to
-                        // populate it
-                        //  once.
-                        SharedPreferences.Editor editor = getSharedPreferences().edit();
-                        editor.clear();
-                        TabModelFilter filter =
-                                mTabModelSelector
-                                        .getTabModelFilterProvider()
-                                        .getTabModelFilter(false);
-                        for (int i = 0; i < filter.getCount(); i++) {
-                            Tab tab = filter.getTabAt(i);
-                            int id = tab.getId();
-                            editor.putString(getUrlKey(id), tab.getUrl().serialize());
-                            editor.putString(getTitleKey(id), tab.getTitle());
-                            editor.putInt(getRootIdKey(id), tab.getRootId());
-                            @Nullable Token tabGroupId = tab.getTabGroupId();
-                            if (tabGroupId != null) {
-                                editor.putLong(getTabGroupIdHighKey(id), tabGroupId.getHigh());
-                                editor.putLong(getTabGroupIdLowKey(id), tabGroupId.getLow());
-                            }
-                            editor.putLong(getTimestampMillisKey(id), tab.getTimestampMillis());
-                        }
-                        editor.apply();
-                        Tab currentTab = mTabModelSelector.getCurrentTab();
-                        if (currentTab != null) cacheLastSearchTerm(currentTab);
-                        filter.addObserver(mTabModelObserver);
-                    }
-                };
-        mTabModelSelector.addObserver(mTabModelSelectorObserver);
-    }
-
-    private static String getTitleKey(int id) {
-        return id + "_title";
-    }
-
-    /**
-     * Get the title of a {@link PseudoTab}.
-     * @param id The ID of the {@link PseudoTab}.
-     * @return The title
-     */
-    public static String getTitle(int id) {
-        return getSharedPreferences().getString(getTitleKey(id), "");
-    }
-
-    private static void cacheTitle(int id, String title) {
-        getSharedPreferences().edit().putString(getTitleKey(id), title).apply();
-    }
-
-    /**
-     * Set the title of a {@link PseudoTab}. Only for testing.
-     * @param id The ID of the {@link PseudoTab}.
-     * @param title The title
-     */
-    public static void setTitleForTesting(int id, String title) {
-        cacheTitle(id, title);
-    }
-
-    private static String getUrlKey(int id) {
-        return id + "_gurl";
-    }
-
-    // Legacy from when URL was serialized as raw string.
-    private static String getDeprecatedUrlKey(int id) {
-        return id + "_url";
-    }
-
-    /**
-     * Get the URL of a {@link PseudoTab}.
-     * @param id The ID of the {@link PseudoTab}.
-     * @return The URL
-     */
-    public static GURL getUrl(int id) {
-        String url = getSharedPreferences().getString(getUrlKey(id), "");
-        if (!url.isEmpty()) {
-            return GURL.deserialize(url);
-        }
-        return new GURL(getSharedPreferences().getString(getDeprecatedUrlKey(id), ""));
-    }
-
-    private static void cacheUrl(int id, GURL url) {
-        getSharedPreferences().edit().putString(getUrlKey(id), url.serialize()).apply();
-    }
-
-    /**
-     * Set the URL of a {@link PseudoTab}.
-     * @param id The ID of the {@link PseudoTab}.
-     * @param url The URL
-     */
-    static void setUrlForTesting(int id, GURL url) {
-        cacheUrl(id, url);
-    }
-
-    private static String getRootIdKey(int id) {
-        return id + "_rootID";
-    }
-
-    /**
-     * Get the root ID of a {@link PseudoTab}.
-     * @param id The ID of the {@link PseudoTab}.
-     * @return The root ID
-     */
-    public static int getRootId(int id) {
-        return getSharedPreferences().getInt(getRootIdKey(id), Tab.INVALID_TAB_ID);
-    }
-
-    private static void cacheRootId(int id, int rootId) {
-        getSharedPreferences().edit().putInt(getRootIdKey(id), rootId).apply();
-    }
-
-    private static String getTabGroupIdHighKey(int id) {
-        return id + "_tabGroupIDHigh";
-    }
-
-    private static String getTabGroupIdLowKey(int id) {
-        return id + "_tabGroupIDLow";
-    }
-
-    /**
-     * Get the tab group ID of a {@link PseudoTab}.
-     *
-     * @param id The ID of the {@link PseudoTab}.
-     * @return The tab group ID
-     */
-    public static @Nullable Token getTabGroupId(int id) {
-        var sharedPrefs = getSharedPreferences();
-        long high = sharedPrefs.getLong(getTabGroupIdHighKey(id), NO_TAB_GROUP_ID);
-        long low = sharedPrefs.getLong(getTabGroupIdLowKey(id), NO_TAB_GROUP_ID);
-        Token tabGroupId = new Token(high, low);
-        return tabGroupId.isZero() ? null : tabGroupId;
-    }
-
-    private static void cacheTabGroupId(int id, @Nullable Token tabGroupId) {
-        var sharedPrefs = getSharedPreferences();
-        if (tabGroupId == null) {
-            sharedPrefs
-                    .edit()
-                    .remove(getTabGroupIdHighKey(id))
-                    .remove(getTabGroupIdLowKey(id))
-                    .apply();
-        } else {
-            sharedPrefs
-                    .edit()
-                    .putLong(getTabGroupIdHighKey(id), tabGroupId.getHigh())
-                    .putLong(getTabGroupIdLowKey(id), tabGroupId.getLow())
-                    .apply();
-        }
-    }
-
-    /**
-     * Set the root ID for a {@link PseudoTab}.
-     * @param id The ID of the {@link PseudoTab}.
-     * @param rootId The root ID
-     */
-    public static void setRootIdForTesting(int id, int rootId) {
-        cacheRootId(id, rootId);
-    }
-
-    /**
-     * Set the tab group ID for a {@link PseudoTab}.
-     *
-     * @param id The ID of the {@link PseudoTab}.
-     * @param tabGroupId The tab group ID
-     */
-    public static void setTabGroupIdForTesting(int id, @Nullable Token tabGroupId) {
-        cacheTabGroupId(id, tabGroupId);
-    }
-
-    private static String getTimestampMillisKey(int id) {
-        return id + "_timestampMillis";
-    }
-
-    /**
-     * Get the timestamp of a {@link PseudoTab}.
-     * @param id The ID of the {@link PseudoTab}.
-     * @return The timestamp
-     */
-    public static long getTimestampMillis(int id) {
-        return getSharedPreferences().getLong(getTimestampMillisKey(id), Tab.INVALID_TIMESTAMP);
-    }
-
-    private static void cacheTimestampMillis(int id, long timestampMillis) {
-        getSharedPreferences().edit().putLong(getTimestampMillisKey(id), timestampMillis).apply();
-    }
-
-    /**
-     * Set the timestamp for a {@link PseudoTab}.
-     * @param id The ID of the {@link PseudoTab}.
-     * @param timestampMillis The timestamp
-     */
-    public static void setTimestampMillisForTesting(int id, long timestampMillis) {
-        cacheTimestampMillis(id, timestampMillis);
-    }
-
-    private static String getLastSearchTermKey(int id) {
-        return id + "_last_search_term";
-    }
-
-    /**
-     * Get the last search term of the default search engine of a {@link PseudoTab} in the
-     * navigation stack.
-     *
-     * @param id The ID of the {@link PseudoTab}.
-     * @return The last search term. Null if none.
-     */
-    public static @Nullable String getLastSearchTerm(int id) {
-        return getSharedPreferences().getString(getLastSearchTermKey(id), null);
-    }
-
-    private static void cacheLastSearchTerm(Tab tab) {
-        if (tab.getWebContents() == null) return;
-        cacheLastSearchTerm(tab.getId(), findLastSearchTerm(tab));
-    }
-
-    private static void cacheLastSearchTerm(int id, String searchTerm) {
-        getSharedPreferences().edit().putString(getLastSearchTermKey(id), searchTerm).apply();
-    }
-
-    /**
-     * Find the latest search term from the navigation stack.
-     * @param tab The tab to find from.
-     * @return The search term. Null for no results.
-     */
-    @VisibleForTesting
-    static @Nullable String findLastSearchTerm(Tab tab) {
-        if (sLastSearchTermProviderForTesting != null) {
-            return sLastSearchTermProviderForTesting.getLastSearchTerm(tab);
-        }
-        assert tab.getWebContents() != null;
-        NavigationController controller = tab.getWebContents().getNavigationController();
-        NavigationHistory history = controller.getNavigationHistory();
-
-        Profile profile = tab.getProfile();
-        TemplateUrlService templateUrlService = TemplateUrlServiceFactory.getForProfile(profile);
-        if (!TextUtils.isEmpty(templateUrlService.getSearchQueryForUrl(tab.getUrl()))) {
-            // If we are already at a search result page, do not show the last search term.
-            return null;
-        }
-
-        for (int i = history.getCurrentEntryIndex() - 1; i >= 0; i--) {
-            GURL url = history.getEntryAtIndex(i).getOriginalUrl();
-            String query = templateUrlService.getSearchQueryForUrl(url);
-            if (!TextUtils.isEmpty(query)) {
-                return removeEscapedCodePoints(query);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * {@link TemplateUrlService#getSearchQueryForUrl(String)} can leave some code points
-     * unescaped for security reasons. See ShouldUnescapeCodePoint().
-     * In our use case, dropping the unescaped code points shouldn't introduce security issues,
-     * and loss of information is fine because the string is not going to be used other than
-     * showing in the UI.
-     * @return the rest of code points
-     */
-    @VisibleForTesting
-    static String removeEscapedCodePoints(String string) {
-        StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < string.length(); i++) {
-            if (string.charAt(i) != '%' || i + 2 >= string.length()) {
-                sb.append(string.charAt(i));
-                continue;
-            }
-            if (Character.digit(string.charAt(i + 1), 16) == -1
-                    || Character.digit(string.charAt(i + 2), 16) == -1) {
-                sb.append(string.charAt(i));
-                continue;
-            }
-            i += 2;
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Set the LastSearchTermProvider for testing.
-     * @param lastSearchTermProvider The mocking object.
-     */
-    static void setLastSearchTermMockForTesting(LastSearchTermProvider lastSearchTermProvider) {
-        sLastSearchTermProviderForTesting = lastSearchTermProvider;
-        ResettersForTesting.register(() -> sLastSearchTermProviderForTesting = null);
-    }
-
-    /**
-     * Set the last search term for a {@link PseudoTab}.
-     * @param id The ID of the {@link PseudoTab}.
-     * @param searchTerm The last search term
-     */
-    public static void setLastSearchTermForTesting(int id, String searchTerm) {
-        cacheLastSearchTerm(id, searchTerm);
-    }
-
-    /** Remove all the observers. */
-    public void destroy() {
-        mTabModelSelectorTabObserver.destroy();
-        TabModelFilter tabModelFilter =
-                mTabModelSelector.getTabModelFilterProvider().getTabModelFilter(false);
-        if (tabModelFilter != null) {
-            tabModelFilter.removeObserver(mTabModelObserver);
-        }
-        mTabModelSelector.removeObserver(mTabModelSelectorObserver);
-    }
-}
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
index a2c9c299..ad36cc5 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MultiThumbnailCardProvider.java
@@ -19,14 +19,12 @@
 import android.util.Size;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabUtils;
@@ -36,7 +34,6 @@
 import org.chromium.chrome.browser.tab_ui.ThumbnailProvider;
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 import org.chromium.url.GURL;
@@ -70,12 +67,12 @@
     private final BrowserControlsStateProvider mBrowserControlsStateProvider;
 
     private class MultiThumbnailFetcher {
-        private final PseudoTab mInitialTab;
+        private final Tab mInitialTab;
         private final Callback<Bitmap> mFinalCallback;
         private final boolean mForceUpdate;
         private final boolean mWriteToCache;
         private final boolean mIsTabSelected;
-        private final List<PseudoTab> mTabs = new ArrayList<>(4);
+        private final List<Tab> mTabs = new ArrayList<>(4);
         private final AtomicInteger mThumbnailsToFetch = new AtomicInteger();
 
         private Canvas mCanvas;
@@ -90,6 +87,7 @@
 
         /**
          * Fetcher that get the thumbnail drawable depending on if the tab is selected.
+         *
          * @see TabContentManager#getTabThumbnailWithCallback
          * @param initialTab Thumbnail is generated for tabs related to initialTab.
          * @param thumbnailSize Desired size of multi-thumbnail.
@@ -98,7 +96,7 @@
          * @param isTabSelected Whether the thumbnail is for a currently selected tab.
          */
         MultiThumbnailFetcher(
-                PseudoTab initialTab,
+                Tab initialTab,
                 Size thumbnailSize,
                 Callback<Bitmap> finalCallback,
                 boolean forceUpdate,
@@ -195,7 +193,7 @@
             }
         }
 
-        private void initializeAndStartFetching(PseudoTab tab) {
+        private void initializeAndStartFetching(Tab initialTab) {
             // Initialize mMultiThumbnailBitmap.
             mMultiThumbnailBitmap =
                     Bitmap.createBitmap(mThumbnailWidth, mThumbnailHeight, Bitmap.Config.ARGB_8888);
@@ -203,37 +201,46 @@
             mCanvas.drawColor(Color.TRANSPARENT);
 
             // Initialize Tabs.
-            List<PseudoTab> relatedTabList =
-                    PseudoTab.getRelatedTabs(mContext, tab, mCurrentTabModelFilterSupplier.get());
+            List<Tab> relatedTabList =
+                    mCurrentTabModelFilterSupplier.get().getRelatedTabList(initialTab.getId());
             if (relatedTabList.size() <= 4) {
-                mThumbnailsToFetch.set(relatedTabList.size());
+                int thumbnailCount = relatedTabList.size();
+                mThumbnailsToFetch.set(thumbnailCount);
 
-                mTabs.add(tab);
-                relatedTabList.remove(tab);
+                mTabs.add(initialTab);
 
-                for (int i = 0; i < 3; i++) {
-                    mTabs.add(i < relatedTabList.size() ? relatedTabList.get(i) : null);
+                for (int i = 0; i < thumbnailCount; i++) {
+                    if (relatedTabList.get(i) == initialTab) continue;
+
+                    mTabs.add(relatedTabList.get(i));
+                }
+                for (int i = 0; i < 4 - thumbnailCount; i++) {
+                    mTabs.add(null);
                 }
             } else {
-                mText = "+" + (relatedTabList.size() - 3);
-                mThumbnailsToFetch.set(3);
+                int thumbnailCount = 3;
+                mText = "+" + (relatedTabList.size() - thumbnailCount);
+                mThumbnailsToFetch.set(thumbnailCount);
 
-                mTabs.add(tab);
-                relatedTabList.remove(tab);
+                mTabs.add(initialTab);
 
-                mTabs.add(relatedTabList.get(0));
-                mTabs.add(relatedTabList.get(1));
+                for (int i = 0; i < thumbnailCount; i++) {
+                    if (relatedTabList.get(i) == initialTab) continue;
+
+                    mTabs.add(relatedTabList.get(i));
+                    if (mTabs.size() == thumbnailCount) break;
+                }
                 mTabs.add(null);
             }
 
             // Fetch and draw all.
             for (int i = 0; i < 4; i++) {
-                PseudoTab pseudoTab = mTabs.get(i);
+                Tab tab = mTabs.get(i);
                 RectF thumbnailRect = mThumbnailRects.get(i);
-                if (pseudoTab != null) {
+                if (tab != null) {
                     final int index = i;
-                    final GURL url = pseudoTab.getUrl();
-                    final boolean isIncognito = pseudoTab.isIncognito();
+                    final GURL url = tab.getUrl();
+                    final boolean isIncognito = tab.isIncognito();
                     final Size tabThumbnailSize =
                             new Size((int) thumbnailRect.width(), (int) thumbnailRect.height());
                     // getTabThumbnailWithCallback() might call the callback up to twice,
@@ -242,10 +249,10 @@
                     // visible flicker.
                     final AtomicReference<Drawable> lastFavicon = new AtomicReference<>();
                     mTabContentManager.getTabThumbnailWithCallback(
-                            pseudoTab.getId(),
+                            tab.getId(),
                             tabThumbnailSize,
                             thumbnail -> {
-                                if (pseudoTab.isClosingOrDestroyed()) return;
+                                if (tab.isClosing() || tab.isDestroyed()) return;
 
                                 drawThumbnailBitmapOnCanvasWithFrame(thumbnail, index);
                                 if (lastFavicon.get() != null) {
@@ -255,7 +262,7 @@
                                             url,
                                             isIncognito,
                                             (Drawable favicon) -> {
-                                                if (pseudoTab.isClosingOrDestroyed()) return;
+                                                if (tab.isClosing() || tab.isDestroyed()) return;
 
                                                 lastFavicon.set(favicon);
                                                 drawFaviconThenMaybeSendBack(favicon, index);
@@ -449,20 +456,13 @@
             boolean writeToCache,
             boolean isSelected) {
         TabModelFilter filter = mCurrentTabModelFilterSupplier.get();
-        PseudoTab pseudoTab = null;
-        boolean useMultiThumbnail = false;
-        if (filter.isTabModelRestored()) {
-            Tab tab = TabModelUtils.getTabById(filter.getTabModel(), tabId);
-            useMultiThumbnail = tab != null && filter.isTabInTabGroup(tab);
-            pseudoTab = tab != null ? PseudoTab.fromTab(tab) : PseudoTab.fromTabId(tabId);
-        } else {
-            pseudoTab = PseudoTab.fromTabId(tabId);
-            useMultiThumbnail = isPseudoTabInTabGroup(filter, pseudoTab);
-        }
+        assert filter.isTabModelRestored();
+        Tab tab = TabModelUtils.getTabById(filter.getTabModel(), tabId);
+        boolean useMultiThumbnail = filter.isTabInTabGroup(tab);
         if (useMultiThumbnail) {
-            assert pseudoTab != null;
+            assert tab != null;
             new MultiThumbnailFetcher(
-                            pseudoTab,
+                            tab,
                             thumbnailSize,
                             finalCallback,
                             forceUpdate,
@@ -474,14 +474,4 @@
         mTabContentManager.getTabThumbnailWithCallback(
                 tabId, thumbnailSize, finalCallback, forceUpdate, writeToCache);
     }
-
-    private boolean isPseudoTabInTabGroup(
-            @NonNull TabModelFilter filter, @Nullable PseudoTab pseudoTab) {
-        if (ChromeFeatureList.sAndroidTabGroupStableIds.isEnabled()) {
-            return pseudoTab != null && pseudoTab.getTabGroupId() != null;
-        } else {
-            return pseudoTab != null
-                    && PseudoTab.getRelatedTabs(mContext, pseudoTab, filter).size() > 1;
-        }
-    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
index c9db8d1..44d4127 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
@@ -206,7 +206,6 @@
                                 tabContentManager.getTabThumbnailWithCallback(
                                         tabId, thumbnailSize, callback, forceUpdate, writeBack);
                             },
-                            null,
                             false,
                             gridCardOnClickListenerProvider,
                             mMediator.getTabGridDialogHandler(),
@@ -449,7 +448,7 @@
 
     @Override
     public void resetWithListOfTabs(@Nullable List<Tab> tabs) {
-        mTabListCoordinator.resetWithListOfTabs(tabs);
+        mTabListCoordinator.resetWithListOfTabs(tabs, false);
         mMediator.onReset(tabs);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
index a0eed745..1506323 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
@@ -180,7 +180,6 @@
                             currentTabModelFilterSupplier,
                             () -> mTabModelSelector.getModel(false),
                             null,
-                            null,
                             false,
                             null,
                             null,
@@ -273,12 +272,12 @@
                     mTabStripCoordinator.getContainerView(),
                     mBottomSheetController);
         }
-        mTabStripCoordinator.resetWithListOfTabs(tabs);
+        mTabStripCoordinator.resetWithListOfTabs(tabs, false);
     }
 
     /**
-     * Handles a reset event originated from {@link TabGroupUiMediator}
-     * when the bottom sheet is expanded or the dialog is shown.
+     * Handles a reset event originated from {@link TabGroupUiMediator} when the bottom sheet is
+     * expanded or the dialog is shown.
      *
      * @param tabs List of Tabs to reset.
      */
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
index 1a40d3e..54004448 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediator.java
@@ -450,8 +450,6 @@
      *     not, associated tabs from #getTabsToShowForID will be showing in the tab strip.
      */
     private void resetTabStripWithRelatedTabsForId(int id) {
-        // TODO(crbug.com/40064910): PseudoTab#getRelatedTabList() requires the tab state to be
-        // initialized. If this is called before tab state is initialized just skip.
         if (!mTabModelSelector.isTabStateInitialized()) return;
 
         // TODO(crbug.com/40133857): We should be able to guard this call behind some checks so that
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
index 8d01c25..7d03bd9 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -43,7 +43,6 @@
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.browser.tasks.ReturnToChromeUtil;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.ModelType;
 import org.chromium.chrome.browser.tasks.tab_management.TabProperties.TabActionState;
@@ -125,7 +124,6 @@
      * @param tabModelFilterSupplier The supplier for the current tab model filter.
      * @param regularTabModelSupplier The supplier for the regular tab model.
      * @param thumbnailProvider Provider to provide screenshot related details.
-     * @param titleProvider Provider for a given tab's title.
      * @param actionOnRelatedTabs Whether tab-related actions should be operated on all related
      *     tabs.
      * @param gridCardOnClickListenerProvider Provides the onClickListener for opening dialog when
@@ -155,7 +153,6 @@
             @NonNull ObservableSupplier<TabModelFilter> tabModelFilterSupplier,
             @NonNull Supplier<TabModel> regularTabModelSupplier,
             @Nullable ThumbnailProvider thumbnailProvider,
-            @Nullable PseudoTab.TitleProvider titleProvider,
             boolean actionOnRelatedTabs,
             @Nullable
                     TabListMediator.GridCardOnClickListenerProvider gridCardOnClickListenerProvider,
@@ -176,7 +173,6 @@
                 tabModelFilterSupplier,
                 regularTabModelSupplier,
                 thumbnailProvider,
-                titleProvider,
                 actionOnRelatedTabs,
                 gridCardOnClickListenerProvider,
                 dialogHandler,
@@ -202,7 +198,6 @@
             @NonNull ObservableSupplier<TabModelFilter> tabModelFilterSupplier,
             @NonNull Supplier<TabModel> regularTabModelSupplier,
             @Nullable ThumbnailProvider thumbnailProvider,
-            @Nullable PseudoTab.TitleProvider titleProvider,
             boolean actionOnRelatedTabs,
             @Nullable
                     TabListMediator.GridCardOnClickListenerProvider gridCardOnClickListenerProvider,
@@ -322,7 +317,6 @@
                         tabModelFilterSupplier,
                         regularTabModelSupplier,
                         thumbnailProvider,
-                        titleProvider,
                         tabListFaviconProvider,
                         new TabGroupColorFaviconProvider(mContext),
                         actionOnRelatedTabs,
@@ -658,14 +652,10 @@
     /**
      * @see TabListMediator#resetWithListOfTabs(List, boolean)
      */
-    boolean resetWithListOfTabs(@Nullable List<PseudoTab> tabs, boolean quickMode) {
+    boolean resetWithListOfTabs(@Nullable List<Tab> tabs, boolean quickMode) {
         return mMediator.resetWithListOfTabs(tabs, quickMode);
     }
 
-    boolean resetWithListOfTabs(@Nullable List<Tab> tabs) {
-        return resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
-    }
-
     void softCleanup() {
         mMediator.softCleanup();
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java
index 6641ec1..0b16f43 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorCoordinator.java
@@ -29,9 +29,6 @@
 import org.chromium.chrome.browser.tab_ui.ThumbnailProvider;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.chrome.browser.tabmodel.TabModelUtils;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
-import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
 import org.chromium.chrome.browser.tasks.tab_management.TabProperties.TabActionState;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiMetricsHelper.TabListEditorExitMetricGroups;
@@ -179,7 +176,6 @@
 
             ThumbnailProvider thumbnailProvider =
                     initThumbnailProvider(displayGroups, tabContentManager);
-            PseudoTab.TitleProvider titleProvider = displayGroups ? this::getTitle : null;
 
             // TODO(ckitagawa): Lazily instantiate the TabListEditorCoordinator. When doing so,
             // the Coordinator hosting the TabListEditorCoordinator could share and reconfigure
@@ -192,7 +188,6 @@
                             currentTabModelFilterSupplier,
                             regularTabModelSupplier,
                             thumbnailProvider,
-                            titleProvider,
                             displayGroups,
                             null,
                             null,
@@ -319,12 +314,13 @@
 
     /**
      * Resets {@link TabListCoordinator} with the provided list.
+     *
      * @param tabs List of {@link Tab}s to reset.
      * @param preSelectedCount First {@code preSelectedCount} {@code tabs} are pre-selected.
      * @param quickMode whether to use quick mode.
      */
     void resetWithListOfTabs(@Nullable List<Tab> tabs, int preSelectedCount, boolean quickMode) {
-        mTabListCoordinator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), quickMode);
+        mTabListCoordinator.resetWithListOfTabs(tabs, quickMode);
 
         if (tabs != null && preSelectedCount > 0 && preSelectedCount < tabs.size()) {
             mTabListCoordinator.addSpecialListItem(
@@ -334,16 +330,6 @@
         }
     }
 
-    private String getTitle(Context context, PseudoTab pseudoTab) {
-        TabGroupModelFilter filter = (TabGroupModelFilter) mCurrentTabModelFilterSupplier.get();
-        Tab tab = TabModelUtils.getTabById(filter.getTabModel(), pseudoTab.getId());
-        assert tab != null;
-        if (!filter.isTabInTabGroup(tab)) return tab.getTitle();
-
-        return TabGroupTitleEditor.getDefaultTitle(
-                context, filter.getRelatedTabCountForRootId(tab.getRootId()));
-    }
-
     private ThumbnailProvider initThumbnailProvider(
             boolean displayGroups, TabContentManager tabContentManager) {
         if (displayGroups) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index a4eace1..2135b3f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -77,7 +77,6 @@
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupColorUtils;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
@@ -374,7 +373,6 @@
     private final ValueChangedCallback<TabModelFilter> mOnTabModelFilterChanged =
             new ValueChangedCallback<>(this::onTabModelFilterChanged);
     private final TabActionListener mTabClosedListener;
-    private final PseudoTab.TitleProvider mTitleProvider;
     private final SelectionDelegateProvider mSelectionDelegateProvider;
     private final GridCardOnClickListenerProvider mGridCardOnClickListenerProvider;
     private final TabGridDialogHandler mTabGridDialogHandler;
@@ -540,12 +538,12 @@
                             .model
                             .set(
                                     TabProperties.TITLE,
-                                    getLatestTitleForTab(PseudoTab.fromTab(updatedTab)));
+                                    getLatestTitleForTab(updatedTab, /* useDefault= */ true));
                 }
 
                 @Override
                 public void onFaviconUpdated(Tab updatedTab, Bitmap icon, GURL iconUrl) {
-                    updateFaviconForTab(PseudoTab.fromTab(updatedTab), icon, iconUrl);
+                    updateFaviconForTab(updatedTab, icon, iconUrl);
                 }
 
                 @Override
@@ -591,12 +589,12 @@
 
                     @Nullable Pair<Integer, Tab> indexAndTab = getIndexAndTabForRootId(rootId);
                     if (indexAndTab == null) return;
-                    PseudoTab pseudoTab = PseudoTab.fromTab(indexAndTab.second);
+                    Tab tab = indexAndTab.second;
                     PropertyModel model = mModel.get(indexAndTab.first).model;
 
                     model.set(TabProperties.TITLE, newTitle);
-                    updateDescriptionString(pseudoTab, model);
-                    updateActionButtonDescriptionString(pseudoTab, model);
+                    updateDescriptionString(tab, model);
+                    updateActionButtonDescriptionString(tab, model);
                 }
 
                 @Override
@@ -607,16 +605,16 @@
 
                     @Nullable Pair<Integer, Tab> indexAndTab = getIndexAndTabForRootId(rootId);
                     if (indexAndTab == null) return;
-                    PseudoTab pseudoTab = PseudoTab.fromTab(indexAndTab.second);
+                    Tab tab = indexAndTab.second;
                     PropertyModel model = mModel.get(indexAndTab.first).model;
 
                     if (mMode == TabListMode.LIST) {
                         model.set(TabProperties.TAB_GROUP_COLOR_ID, newColor);
                     } else if (mMode == TabListMode.GRID) {
-                        updateFaviconForTab(pseudoTab, null, null);
+                        updateFaviconForTab(tab, null, null);
                     }
-                    updateDescriptionString(pseudoTab, model);
-                    updateActionButtonDescriptionString(pseudoTab, model);
+                    updateDescriptionString(tab, model);
+                    updateActionButtonDescriptionString(tab, model);
                 }
 
                 @Override
@@ -691,14 +689,14 @@
                                 && movedTab != previousGroupTab) {
                             int filterIndex = filter.indexOf(movedTab);
                             addTabInfoToModel(
-                                    PseudoTab.fromTab(movedTab),
+                                    movedTab,
                                     mModel.indexOfNthTabCard(filterIndex),
                                     currentSelectedTabId == movedTab.getId());
                         }
                         boolean isSelected = currentSelectedTabId == previousGroupTab.getId();
                         updateTab(
                                 mModel.indexOfNthTabCard(prevFilterIndex),
-                                PseudoTab.fromTab(previousGroupTab),
+                                previousGroupTab,
                                 isSelected,
                                 true,
                                 false);
@@ -751,7 +749,7 @@
                                     TabModelUtils.getTabById(
                                             tabModel,
                                             mModel.get(desIndex).model.get(TabProperties.TAB_ID));
-                            updateTab(desIndex, PseudoTab.fromTab(tab), isSelected, false, false);
+                            updateTab(desIndex, tab, isSelected, false, false);
                             return;
                         }
 
@@ -775,12 +773,7 @@
                         boolean isSelected =
                                 TabModelUtils.getCurrentTab(tabModel)
                                         == newSelectedTabInMergedGroup;
-                        updateTab(
-                                desIndex,
-                                PseudoTab.fromTab(newSelectedTabInMergedGroup),
-                                isSelected,
-                                true,
-                                false);
+                        updateTab(desIndex, newSelectedTabInMergedGroup, isSelected, true, false);
                     } else {
                         // If the model is empty we can't check if the added tab is part of the
                         // current group. Assume it isn't since a group state with 0 tab should be
@@ -888,7 +881,6 @@
      * @param modalDialogManager The {@link ModalDialogManager} for managing dialog lifecycles.
      * @param regularTabModelSupplier The supplier of the regular {@link TabModel}.
      * @param thumbnailProvider {@link ThumbnailProvider} to provide screenshot related details.
-     * @param titleProvider {@link PseudoTab.TitleProvider} for a given tab's title to show.
      * @param tabListFaviconProvider Provider for all favicon related drawables.
      * @param tabGroupColorFaviconProvider Provider for tab group color favicon related drawables.
      * @param actionOnRelatedTabs Whether tab-related actions should be operated on all related
@@ -912,7 +904,6 @@
             @NonNull ObservableSupplier<TabModelFilter> tabModelFilterSupplier,
             @NonNull Supplier<TabModel> regularTabModelSupplier,
             @Nullable ThumbnailProvider thumbnailProvider,
-            @Nullable PseudoTab.TitleProvider titleProvider,
             TabListFaviconProvider tabListFaviconProvider,
             @NonNull TabGroupColorFaviconProvider tabGroupColorFaviconProvider,
             boolean actionOnRelatedTabs,
@@ -933,7 +924,6 @@
         mTabListFaviconProvider = tabListFaviconProvider;
         mTabGroupColorFaviconProvider = tabGroupColorFaviconProvider;
         mComponentName = componentName;
-        mTitleProvider = titleProvider;
         mSelectionDelegateProvider = selectionDelegateProvider;
         mGridCardOnClickListenerProvider = gridCardOnClickListenerProvider;
         mTabGridDialogHandler = dialogHandler;
@@ -1014,7 +1004,7 @@
 
                             updateTab(
                                     tabListModelIndex,
-                                    PseudoTab.fromTab(currentGroupSelectedTab),
+                                    currentGroupSelectedTab,
                                     mModel.get(tabListModelIndex)
                                             .model
                                             .get(TabProperties.IS_SELECTED),
@@ -1061,7 +1051,7 @@
                             }
                             updateTab(
                                     tabListModelIndex,
-                                    PseudoTab.fromTab(currentGroupSelectedTab),
+                                    currentGroupSelectedTab,
                                     mModel.get(tabListModelIndex)
                                             .model
                                             .get(TabProperties.IS_SELECTED),
@@ -1085,12 +1075,7 @@
                                 final int currentSelectedTabId =
                                         TabModelUtils.getCurrentTabId(groupFilter.getTabModel());
                                 boolean isSelected = currentSelectedTabId == groupTab.getId();
-                                updateTab(
-                                        groupIndex,
-                                        PseudoTab.fromTab(groupTab),
-                                        isSelected,
-                                        true,
-                                        false);
+                                updateTab(groupIndex, groupTab, isSelected, true, false);
 
                                 return;
                             }
@@ -1279,9 +1264,8 @@
                         int index = mModel.indexFromId(currentGroupSelectedTab.getId());
                         if (index == TabModel.INVALID_TAB_INDEX) return;
                         mModel.get(index).model.set(TabProperties.TITLE, title);
-                        updateDescriptionString(PseudoTab.fromTab(tab), mModel.get(index).model);
-                        updateActionButtonDescriptionString(
-                                PseudoTab.fromTab(tab), mModel.get(index).model);
+                        updateDescriptionString(tab, mModel.get(index).model);
+                        updateActionButtonDescriptionString(tab, mModel.get(index).model);
                     }
 
                     @Override
@@ -1404,7 +1388,7 @@
 
         Tab currentTab =
                 TabModelUtils.getCurrentTab(mCurrentTabModelFilterSupplier.get().getTabModel());
-        addTabInfoToModel(PseudoTab.fromTab(tab), newIndex, currentTab == tab);
+        addTabInfoToModel(tab, newIndex, currentTab == tab);
         return newIndex;
     }
 
@@ -1412,7 +1396,7 @@
         return position != TabModel.INVALID_TAB_INDEX && position < mModel.size();
     }
 
-    private boolean areTabsUnchanged(@Nullable List<PseudoTab> tabs) {
+    private boolean areTabsUnchanged(@Nullable List<Tab> tabs) {
         int tabsCount = 0;
         for (int i = 0; i < mModel.size(); i++) {
             if (mModel.get(i).model.get(CARD_TYPE) == TAB) {
@@ -1441,17 +1425,16 @@
      * @param quickMode Whether to skip capturing the selected live tab for the thumbnail.
      * @return Whether the {@link TabListRecyclerView} can be shown quickly.
      */
-    boolean resetWithListOfTabs(@Nullable List<PseudoTab> tabs, boolean quickMode) {
-        List<PseudoTab> tabsList = tabs;
-        mVisible = tabsList != null;
+    boolean resetWithListOfTabs(@Nullable List<Tab> tabs, boolean quickMode) {
+        mVisible = tabs != null;
         if (tabs != null) {
             recordPriceAnnotationsEnabledMetrics();
         }
         TabModelFilter filter = mCurrentTabModelFilterSupplier.get();
-        if (areTabsUnchanged(tabsList)) {
-            if (tabsList == null) return true;
-            for (int i = 0; i < tabsList.size(); i++) {
-                PseudoTab tab = tabsList.get(i);
+        if (areTabsUnchanged(tabs)) {
+            if (tabs == null) return true;
+            for (int i = 0; i < tabs.size(); i++) {
+                Tab tab = tabs.get(i);
                 int currentTabId = TabModelUtils.getCurrentTabId(filter.getTabModel());
                 boolean isSelected = isSelectedTab(tab, currentTabId);
                 updateTab(mModel.indexOfNthTabCard(i), tab, isSelected, false, quickMode);
@@ -1461,13 +1444,13 @@
         mModel.set(new ArrayList<>());
         mLastSelectedTabListModelIndex = TabList.INVALID_TAB_INDEX;
 
-        if (tabsList == null) {
+        if (tabs == null) {
             return true;
         }
         int currentTabId = TabModelUtils.getCurrentTabId(filter.getTabModel());
 
-        for (int i = 0; i < tabsList.size(); i++) {
-            PseudoTab tab = tabsList.get(i);
+        for (int i = 0; i < tabs.size(); i++) {
+            Tab tab = tabs.get(i);
             addTabInfoToModel(tab, i, isSelectedTab(tab, currentTabId));
         }
 
@@ -1497,7 +1480,7 @@
         }
     }
 
-    private boolean isSelectedTab(PseudoTab tab, int tabModelSelectedTabId) {
+    private boolean isSelectedTab(Tab tab, int tabModelSelectedTabId) {
         SelectionDelegate<Integer> selectionDelegate = getTabSelectionDelegate();
         if (selectionDelegate == null) {
             return tab.getId() == tabModelSelectedTabId;
@@ -1551,36 +1534,26 @@
     }
 
     private void updateTab(
-            int index,
-            PseudoTab pseudoTab,
-            boolean isSelected,
-            boolean isUpdatingId,
-            boolean quickMode) {
+            int index, Tab tab, boolean isSelected, boolean isUpdatingId, boolean quickMode) {
         if (index < 0 || index >= mModel.size()) return;
         if (isUpdatingId) {
-            mModel.get(index).model.set(TabProperties.TAB_ID, pseudoTab.getId());
+            mModel.get(index).model.set(TabProperties.TAB_ID, tab.getId());
         } else {
-            assert mModel.get(index).model.get(TabProperties.TAB_ID) == pseudoTab.getId();
+            assert mModel.get(index).model.get(TabProperties.TAB_ID) == tab.getId();
         }
 
         // TODO(wychen): refactor this.
-        boolean isRealTab = pseudoTab.hasRealTab();
-        boolean isInTabGroup = isPseudoTabInTabGroup(pseudoTab);
+        boolean isInTabGroup = isTabInTabGroup(tab);
         TabActionListener tabSelectedListener;
-        if (!isRealTab) {
-            tabSelectedListener = null;
+        if (mGridCardOnClickListenerProvider == null
+                || !isInTabGroup
+                || !mActionsOnAllRelatedTabs) {
+            tabSelectedListener = mTabSelectedListener;
         } else {
-            if (mGridCardOnClickListenerProvider == null
-                    || !isInTabGroup
-                    || !mActionsOnAllRelatedTabs) {
-                tabSelectedListener = mTabSelectedListener;
-            } else {
-                tabSelectedListener =
-                        mGridCardOnClickListenerProvider.openTabGridDialog(pseudoTab.getTab());
+            tabSelectedListener = mGridCardOnClickListenerProvider.openTabGridDialog(tab);
 
-                if (tabSelectedListener == null) {
-                    tabSelectedListener = mTabSelectedListener;
-                }
+            if (tabSelectedListener == null) {
+                tabSelectedListener = mTabSelectedListener;
             }
         }
 
@@ -1589,9 +1562,9 @@
             if (mMode == TabListMode.LIST && isInTabGroup) {
                 final @TabGroupColorId int tabGroupColorId =
                         TabGroupColorUtils.getOrCreateTabGroupColor(
-                                pseudoTab.getRootId(),
+                                tab.getRootId(),
                                 (TabGroupModelFilter) mCurrentTabModelFilterSupplier.get());
-                PropertyModel model = getModelFromId(pseudoTab.getId());
+                PropertyModel model = getModelFromId(tab.getId());
                 if (model != null) {
                     model.set(TabProperties.TAB_GROUP_COLOR_ID, tabGroupColorId);
                 }
@@ -1601,17 +1574,19 @@
         mModel.get(index).model.set(TabProperties.TAB_SELECTED_LISTENER, tabSelectedListener);
         mModel.get(index).model.set(TabProperties.IS_SELECTED, isSelected);
         mModel.get(index).model.set(TabProperties.SHOULD_SHOW_PRICE_DROP_TOOLTIP, false);
-        mModel.get(index).model.set(TabProperties.TITLE, getLatestTitleForTab(pseudoTab));
+        mModel.get(index)
+                .model
+                .set(TabProperties.TITLE, getLatestTitleForTab(tab, /* useDefault= */ true));
 
         mModel.get(index)
                 .model
                 .set(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK, mOnMenuItemClickedCallback);
         // A tab is deemed a tab group card representation if it is part of a tab group and
         // based in the tab switcher.
-        boolean isTabGroup = isPseudoTabInTabGroup(pseudoTab) && isParentComponentTabSwitcher();
+        boolean isTabGroup = isTabInTabGroup(tab) && isParentComponentTabSwitcher();
         // Update the group color icon.
         if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled() && isTabGroup) {
-            updateFaviconForTab(pseudoTab, null, null);
+            updateFaviconForTab(tab, null, null);
         }
         // The ordering of TAB_ACTION_BUTTON_LISTENER and IS_TAB_GROUP must be preserved when
         // setting the property keys on the model. Both properties modify the onClickListener
@@ -1620,9 +1595,7 @@
         if (!ChromeFeatureList.sTabGroupPaneAndroid.isEnabled() || !isTabGroup) {
             mModel.get(index)
                     .model
-                    .set(
-                            TabProperties.TAB_ACTION_BUTTON_LISTENER,
-                            isRealTab ? mTabClosedListener : null);
+                    .set(TabProperties.TAB_ACTION_BUTTON_LISTENER, mTabClosedListener);
         }
         // Only set this for tab group representation cards. An onClickListener will be set in the
         // view as part of the accompanying logic.
@@ -1636,17 +1609,13 @@
                                     isTabGroup));
         }
 
-        updateDescriptionString(pseudoTab, mModel.get(index).model);
-        updateActionButtonDescriptionString(pseudoTab, mModel.get(index).model);
-        if (isRealTab) {
-            mModel.get(index)
-                    .model
-                    .set(TabProperties.URL_DOMAIN, getDomainForTab(pseudoTab.getTab()));
-        }
+        updateDescriptionString(tab, mModel.get(index).model);
+        updateActionButtonDescriptionString(tab, mModel.get(index).model);
+        mModel.get(index).model.set(TabProperties.URL_DOMAIN, getDomainForTab(tab));
 
-        setupPersistedTabDataFetcherForTab(pseudoTab, index);
+        setupPersistedTabDataFetcherForTab(tab, index);
 
-        updateFaviconForTab(pseudoTab, null, null);
+        updateFaviconForTab(tab, null, null);
         boolean forceUpdate = isSelected && !quickMode;
         boolean forceUpdateLastSelected =
                 mActionsOnAllRelatedTabs && index == mLastSelectedTabListModelIndex && !quickMode;
@@ -1663,7 +1632,7 @@
             ThumbnailFetcher callback =
                     new ThumbnailFetcher(
                             mThumbnailProvider,
-                            pseudoTab.getId(),
+                            tab.getId(),
                             (forceUpdate || forceUpdateLastSelected) && !isSelectable,
                             forceUpdate
                                     && !TabUiFeatureUtilities.isTabToGtsAnimationEnabled(mContext)
@@ -1673,18 +1642,11 @@
     }
 
     @VisibleForTesting
-    public boolean isPseudoTabInTabGroup(@NonNull PseudoTab pseudoTab) {
+    public boolean isTabInTabGroup(@NonNull Tab tab) {
         TabModelFilter filter = mCurrentTabModelFilterSupplier.get();
-        if (filter.isTabModelRestored()) {
-            Tab tab = TabModelUtils.getTabById(filter.getTabModel(), pseudoTab.getId());
-            return filter.isTabInTabGroup(tab);
-        } else {
-            if (ChromeFeatureList.sAndroidTabGroupStableIds.isEnabled()) {
-                return pseudoTab.getTabGroupId() != null;
-            } else {
-                return PseudoTab.getRelatedTabs(mContext, pseudoTab, filter).size() > 1;
-            }
-        }
+        assert filter.isTabModelRestored();
+
+        return filter.isTabInTabGroup(tab);
     }
 
     public Set<Integer> getViewedTabIdsForTesting() {
@@ -1874,24 +1836,20 @@
         }
     }
 
-    private void addTabInfoToModel(final PseudoTab pseudoTab, int index, boolean isSelected) {
+    private void addTabInfoToModel(Tab tab, int index, boolean isSelected) {
         assert index != TabModel.INVALID_TAB_INDEX;
         boolean showIPH = false;
-        boolean isRealTab = pseudoTab.hasRealTab();
-        boolean isInTabGroup = isPseudoTabInTabGroup(pseudoTab);
-        if (mActionsOnAllRelatedTabs && !mShownIPH && isRealTab) {
+        boolean isInTabGroup = isTabInTabGroup(tab);
+        if (mActionsOnAllRelatedTabs && !mShownIPH) {
             showIPH = isInTabGroup;
         }
         TabActionListener tabSelectedListener;
-        if (!isRealTab) {
-            tabSelectedListener = null;
-        } else if (mGridCardOnClickListenerProvider == null
+        if (mGridCardOnClickListenerProvider == null
                 || !isInTabGroup
                 || !mActionsOnAllRelatedTabs) {
             tabSelectedListener = mTabSelectedListener;
         } else {
-            tabSelectedListener =
-                    mGridCardOnClickListenerProvider.openTabGridDialog(pseudoTab.getTab());
+            tabSelectedListener = mGridCardOnClickListenerProvider.openTabGridDialog(tab);
             if (tabSelectedListener == null) {
                 tabSelectedListener = mTabSelectedListener;
             }
@@ -1906,28 +1864,27 @@
             // Rather it's LIST mode where we do this, and additionally not when we've opened a
             // dialog for a particular group, checked by isParentComponentTabSwitcher().
             if (mMode == TabListMode.LIST && isInTabGroup && isParentComponentTabSwitcher()) {
-                colorId =
-                        TabGroupColorUtils.getOrCreateTabGroupColor(pseudoTab.getRootId(), filter);
+                colorId = TabGroupColorUtils.getOrCreateTabGroupColor(tab.getRootId(), filter);
             }
         }
 
         int selectedTabBackgroundDrawableId =
-                pseudoTab.isIncognito()
+                tab.isIncognito()
                         ? R.drawable.selected_tab_background_incognito
                         : R.drawable.selected_tab_background;
 
         int tabstripFaviconBackgroundDrawableId =
-                pseudoTab.isIncognito()
+                tab.isIncognito()
                         ? R.color.favicon_background_color_incognito
                         : R.color.favicon_background_color;
         PropertyModel tabInfo =
                 new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
                         .with(TabProperties.TAB_ACTION_STATE, mTabActionState)
-                        .with(TabProperties.TAB_ID, pseudoTab.getId())
-                        .with(TabProperties.TITLE, getLatestTitleForTab(pseudoTab))
+                        .with(TabProperties.TAB_ID, tab.getId())
                         .with(
-                                TabProperties.URL_DOMAIN,
-                                isRealTab ? getDomainForTab(pseudoTab.getTab()) : null)
+                                TabProperties.TITLE,
+                                getLatestTitleForTab(tab, /* useDefault= */ true))
+                        .with(TabProperties.URL_DOMAIN, getDomainForTab(tab))
                         .with(TabProperties.FAVICON_FETCHER, null)
                         .with(TabProperties.FAVICON_FETCHED, false)
                         .with(TabProperties.IS_SELECTED, isSelected)
@@ -1936,10 +1893,8 @@
                         .with(
                                 TabProperties.CARD_ANIMATION_STATUS,
                                 TabGridView.AnimationStatus.CARD_RESTORE)
-                        .with(
-                                TabProperties.TAB_SELECTION_DELEGATE,
-                                isRealTab ? getTabSelectionDelegate() : null)
-                        .with(TabProperties.IS_INCOGNITO, pseudoTab.isIncognito())
+                        .with(TabProperties.TAB_SELECTION_DELEGATE, getTabSelectionDelegate())
+                        .with(TabProperties.IS_INCOGNITO, tab.isIncognito())
                         .with(
                                 TabProperties.SELECTED_TAB_BACKGROUND_DRAWABLE_ID,
                                 selectedTabBackgroundDrawableId)
@@ -1955,10 +1910,10 @@
                         .with(TabProperties.TAB_GROUP_COLOR_ID, colorId)
                         .build();
 
-        if (!mActionsOnAllRelatedTabs || !isPseudoTabInTabGroup(pseudoTab)) {
+        if (!mActionsOnAllRelatedTabs || !isTabInTabGroup(tab)) {
             tabInfo.set(
                     TabProperties.FAVICON_FETCHER,
-                    mTabListFaviconProvider.getDefaultFaviconFetcher(pseudoTab.isIncognito()));
+                    mTabListFaviconProvider.getDefaultFaviconFetcher(tab.isIncognito()));
         }
 
         if (mTabActionState == TabActionState.SELECTABLE) {
@@ -1967,20 +1922,20 @@
             // dark.
             ColorStateList checkedDrawableColorList =
                     ColorStateList.valueOf(
-                            pseudoTab.isIncognito()
+                            tab.isIncognito()
                                     ? mContext.getColor(R.color.default_icon_color_dark)
                                     : SemanticColorUtils.getDefaultIconColorInverse(mContext));
             ColorStateList actionButtonBackgroundColorList =
                     AppCompatResources.getColorStateList(
                             mContext,
-                            pseudoTab.isIncognito()
+                            tab.isIncognito()
                                     ? R.color.default_icon_color_light
                                     : R.color.default_icon_color_tint_list);
             // TODO(crbug.com/41477267): Update color baseline_primary_80 to active_color_dark when
             // the associated bug is landed.
             ColorStateList actionbuttonSelectedBackgroundColorList =
                     ColorStateList.valueOf(
-                            pseudoTab.isIncognito()
+                            tab.isIncognito()
                                     ? mContext.getColor(R.color.baseline_primary_80)
                                     : SemanticColorUtils.getDefaultControlColorActive(mContext));
 
@@ -1999,15 +1954,13 @@
             tabInfo.set(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK, mOnMenuItemClickedCallback);
             // A tab is deemed a tab group card representation if it is part of a tab group and
             // based in the tab switcher.
-            boolean isTabGroup = isPseudoTabInTabGroup(pseudoTab) && isParentComponentTabSwitcher();
+            boolean isTabGroup = isTabInTabGroup(tab) && isParentComponentTabSwitcher();
             // The ordering of TAB_ACTION_BUTTON_LISTENER and IS_TAB_GROUP must be preserved when
             // setting the property keys on the model. Both properties modify the onClickListener
             // so ensure that the default behavior (close on click) is set first, and tab groups
             // under valid circumstances will override the listener with alternate behavior.
             if (!ChromeFeatureList.sTabGroupPaneAndroid.isEnabled() || !isTabGroup) {
-                tabInfo.set(
-                        TabProperties.TAB_ACTION_BUTTON_LISTENER,
-                        isRealTab ? mTabClosedListener : null);
+                tabInfo.set(TabProperties.TAB_ACTION_BUTTON_LISTENER, mTabClosedListener);
             }
             // Only set this for tab group representation cards. An onClickListener will be set in
             // the view as part of the accompanying logic.
@@ -2018,8 +1971,8 @@
                                 TabGroupSyncFeatures.isTabGroupSyncEnabled(mProfile), isTabGroup));
             }
 
-            updateDescriptionString(pseudoTab, tabInfo);
-            updateActionButtonDescriptionString(pseudoTab, tabInfo);
+            updateDescriptionString(tab, tabInfo);
+            updateActionButtonDescriptionString(tab, tabInfo);
         }
 
         @UiType
@@ -2031,9 +1984,9 @@
             mModel.add(index, new SimpleRecyclerViewAdapter.ListItem(tabUiType, tabInfo));
         }
 
-        setupPersistedTabDataFetcherForTab(pseudoTab, index);
+        setupPersistedTabDataFetcherForTab(tab, index);
 
-        updateFaviconForTab(pseudoTab, null, null);
+        updateFaviconForTab(tab, null, null);
 
         if (mThumbnailProvider != null && mDefaultGridCardSize != null) {
             if (!mDefaultGridCardSize.equals(tabInfo.get(TabProperties.GRID_CARD_SIZE))) {
@@ -2048,17 +2001,16 @@
             ThumbnailFetcher callback =
                     new ThumbnailFetcher(
                             mThumbnailProvider,
-                            pseudoTab.getId(),
+                            tab.getId(),
                             isSelected && !isSelectable,
                             isSelected
                                     && !TabUiFeatureUtilities.isTabToGtsAnimationEnabled(mContext)
                                     && !isSelectable);
             tabInfo.set(TabProperties.THUMBNAIL_FETCHER, callback);
         }
-        if (pseudoTab.getTab() != null) pseudoTab.getTab().addObserver(mTabObserver);
+        tab.addObserver(mTabObserver);
     }
 
-    // TODO(wychen): make this work with PseudoTab.
     private String getDomainForTab(Tab tab) {
         if (!mActionsOnAllRelatedTabs) return getDomain(tab);
 
@@ -2074,13 +2026,12 @@
         return TextUtils.join(", ", domainNames);
     }
 
-    private void updateDescriptionString(PseudoTab pseudoTab, PropertyModel model) {
+    private void updateDescriptionString(Tab tab, PropertyModel model) {
         if (!mActionsOnAllRelatedTabs) return;
-        boolean isInTabGroup = isPseudoTabInTabGroup(pseudoTab);
-        int numOfRelatedTabs = getRelatedTabsForId(pseudoTab.getId()).size();
+        boolean isInTabGroup = isTabInTabGroup(tab);
+        int numOfRelatedTabs = getRelatedTabsForId(tab.getId()).size();
         if (isInTabGroup) {
-            String title = getLatestTitleForTab(pseudoTab);
-            title = title.equals(pseudoTab.getTitle(mContext, mTitleProvider)) ? "" : title;
+            String title = getLatestTitleForTab(tab, /* useDefault= */ false);
             Resources res = mContext.getResources();
             if (!ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
                 model.set(
@@ -2096,7 +2047,7 @@
                                         title,
                                         numOfRelatedTabs));
             } else {
-                int colorId = TabGroupColorUtils.getTabGroupColor(pseudoTab.getRootId());
+                int colorId = TabGroupColorUtils.getTabGroupColor(tab.getRootId());
                 // This should never be the case in practice, but if the color is invalid then set
                 // it to the first color in the list.
                 if (colorId == INVALID_COLOR_ID) {
@@ -2127,17 +2078,15 @@
         }
     }
 
-    private void updateActionButtonDescriptionString(PseudoTab pseudoTab, PropertyModel model) {
+    private void updateActionButtonDescriptionString(Tab tab, PropertyModel model) {
         if (mActionsOnAllRelatedTabs) {
-            boolean isInTabGroup = isPseudoTabInTabGroup(pseudoTab);
-            int numOfRelatedTabs = getRelatedTabsForId(pseudoTab.getId()).size();
+            boolean isInTabGroup = isTabInTabGroup(tab);
+            int numOfRelatedTabs = getRelatedTabsForId(tab.getId()).size();
             if (isInTabGroup) {
-                String title = getLatestTitleForTab(pseudoTab);
-                title = title.equals(pseudoTab.getTitle(mContext, mTitleProvider)) ? "" : title;
+                String title = getLatestTitleForTab(tab, /* useDefault= */ false);
 
                 String descriptionString =
-                        getActionButtonDescriptionString(
-                                numOfRelatedTabs, title, pseudoTab.getRootId());
+                        getActionButtonDescriptionString(numOfRelatedTabs, title, tab.getRootId());
                 model.set(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING, descriptionString);
                 return;
             }
@@ -2145,8 +2094,7 @@
 
         model.set(
                 ACTION_BUTTON_DESCRIPTION_STRING,
-                mContext.getString(
-                        R.string.accessibility_tabstrip_btn_close_tab, pseudoTab.getTitle()));
+                mContext.getString(R.string.accessibility_tabstrip_btn_close_tab, tab.getTitle()));
     }
 
     @VisibleForTesting
@@ -2176,15 +2124,24 @@
     }
 
     @VisibleForTesting
-    String getLatestTitleForTab(PseudoTab pseudoTab) {
-        String originalTitle = pseudoTab.getTitle(mContext, mTitleProvider);
-        if (!mActionsOnAllRelatedTabs || mTabGroupTitleEditor == null) return originalTitle;
-        // If the group degrades to a single tab, delete the stored title.
-        if (!isPseudoTabInTabGroup(pseudoTab)) {
+    String getLatestTitleForTab(Tab tab, boolean useDefault) {
+        String originalTitle = tab.getTitle();
+        if (!mActionsOnAllRelatedTabs || mTabGroupTitleEditor == null || !isTabInTabGroup(tab)) {
             return originalTitle;
         }
-        String storedTitle = mTabGroupTitleEditor.getTabGroupTitle(pseudoTab.getRootId());
-        return storedTitle == null ? originalTitle : storedTitle;
+
+        String storedTitle = mTabGroupTitleEditor.getTabGroupTitle(tab.getRootId());
+        if (storedTitle == null) {
+            if (useDefault) {
+                TabGroupModelFilter filter =
+                        (TabGroupModelFilter) mCurrentTabModelFilterSupplier.get();
+                return TabGroupTitleEditor.getDefaultTitle(
+                        mContext, filter.getRelatedTabCountForRootId(tab.getRootId()));
+            } else {
+                return "";
+            }
+        }
+        return storedTitle;
     }
 
     int selectedTabId() {
@@ -2195,18 +2152,17 @@
         return TabModelUtils.getCurrentTabId(mCurrentTabModelFilterSupplier.get().getTabModel());
     }
 
-    private void setupPersistedTabDataFetcherForTab(PseudoTab pseudoTab, int index) {
-        if (mMode == TabListMode.GRID && pseudoTab.hasRealTab() && !pseudoTab.isIncognito()) {
+    private void setupPersistedTabDataFetcherForTab(Tab tab, int index) {
+        if (mMode == TabListMode.GRID && !tab.isIncognito()) {
             assert mProfile != null;
             if (PriceTrackingUtilities.isTrackPricesOnTabsEnabled(mProfile)
-                    && !isPseudoTabInTabGroup(pseudoTab)) {
+                    && !isTabInTabGroup(tab)) {
                 mModel.get(index)
                         .model
                         .set(
                                 TabProperties.SHOPPING_PERSISTED_TAB_DATA_FETCHER,
                                 new ShoppingPersistedTabDataFetcher(
-                                        pseudoTab.getTab(),
-                                        mPriceWelcomeMessageControllerSupplier));
+                                        tab, mPriceWelcomeMessageControllerSupplier));
             } else {
                 mModel.get(index)
                         .model
@@ -2218,12 +2174,12 @@
     }
 
     @VisibleForTesting
-    void updateFaviconForTab(PseudoTab pseudoTab, @Nullable Bitmap icon, @Nullable GURL iconUrl) {
-        int modelIndex = mModel.indexFromId(pseudoTab.getId());
+    void updateFaviconForTab(Tab tab, @Nullable Bitmap icon, @Nullable GURL iconUrl) {
+        int modelIndex = mModel.indexFromId(tab.getId());
         if (modelIndex == Tab.INVALID_TAB_ID) return;
 
-        if (mActionsOnAllRelatedTabs && isPseudoTabInTabGroup(pseudoTab)) {
-            List<Tab> relatedTabList = getRelatedTabsForId(pseudoTab.getId());
+        if (mActionsOnAllRelatedTabs && isTabInTabGroup(tab)) {
+            List<Tab> relatedTabList = getRelatedTabsForId(tab.getId());
             if (mMode != TabListMode.LIST) {
                 // For tab group card in grid tab switcher, the favicon is set to be null.
                 // With tab group colors, set the the favicon fetcher to a circle of color.
@@ -2231,7 +2187,7 @@
                 if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
                     TabGroupModelFilter filter =
                             (TabGroupModelFilter) mCurrentTabModelFilterSupplier.get();
-                    int colorId = TabGroupColorUtils.getTabGroupColor(pseudoTab.getRootId());
+                    int colorId = TabGroupColorUtils.getTabGroupColor(tab.getRootId());
                     faviconFetcher =
                             mTabGroupColorFaviconProvider.getFaviconFromTabGroupColorFetcher(
                                     colorId, filter.getTabModel().isIncognito());
@@ -2242,9 +2198,9 @@
             } else if (mMode == TabListMode.LIST && relatedTabList.size() > 1) {
                 // The order of the url list matches the multi-thumbnail.
                 List<GURL> urls = new ArrayList<>();
-                urls.add(pseudoTab.getUrl());
+                urls.add(tab.getUrl());
                 for (int i = 0; urls.size() < 4 && i < relatedTabList.size(); i++) {
-                    if (pseudoTab.getId() == relatedTabList.get(i).getId()) continue;
+                    if (tab.getId() == relatedTabList.get(i).getId()) continue;
                     urls.add(relatedTabList.get(i).getUrl());
                 }
 
@@ -2254,7 +2210,7 @@
                         .set(
                                 TabProperties.FAVICON_FETCHER,
                                 mTabListFaviconProvider.getComposedFaviconImageFetcher(
-                                        urls, pseudoTab.isIncognito()));
+                                        urls, tab.isIncognito()));
                 return;
             }
         }
@@ -2273,8 +2229,7 @@
         }
 
         TabFaviconFetcher fetcher =
-                mTabListFaviconProvider.getFaviconForUrlFetcher(
-                        pseudoTab.getUrl(), pseudoTab.isIncognito());
+                mTabListFaviconProvider.getFaviconForUrlFetcher(tab.getUrl(), tab.isIncognito());
         mModel.get(modelIndex).model.set(TabProperties.FAVICON_FETCHER, fetcher);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
index 3f25d9ae..1512f51e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
@@ -48,8 +48,6 @@
 import org.chromium.chrome.browser.tabmodel.TabList;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
-import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
 import org.chromium.chrome.browser.tasks.tab_management.suggestions.TabSuggestionsOrchestrator;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
@@ -201,22 +199,6 @@
                             tabContentManager,
                             currentTabModelFilterSupplier);
 
-            PseudoTab.TitleProvider titleProvider =
-                    (context, pseudoTab) -> {
-                        TabGroupModelFilter filter =
-                                (TabGroupModelFilter)
-                                        tabModelSelector
-                                                .getTabModelFilterProvider()
-                                                .getCurrentTabModelFilterSupplier()
-                                                .get();
-                        Tab tab = TabModelUtils.getTabById(filter.getTabModel(), pseudoTab.getId());
-                        assert tab != null;
-                        if (!filter.isTabInTabGroup(tab)) return tab.getTitle();
-
-                        return TabGroupTitleEditor.getDefaultTitle(
-                                context, filter.getRelatedTabCountForRootId(tab.getRootId()));
-                    };
-
             long startTimeMs = SystemClock.uptimeMillis();
 
             int emptyImageResId =
@@ -239,7 +221,6 @@
                             currentTabModelFilterSupplier,
                             () -> tabModelSelector.getModel(false),
                             mMultiThumbnailCardProvider,
-                            titleProvider,
                             true,
                             mMediator,
                             null,
@@ -298,6 +279,7 @@
                             snackbarManager,
                             modalDialogManager,
                             mTabListCoordinator,
+                            /* visibilitySupplier= */ () -> true,
                             tabListEditorControllerSupplier,
                             mMediator,
                             mode);
@@ -534,11 +516,11 @@
     // ResetHandler implementation.
     @Override
     public boolean resetWithTabList(@Nullable TabList tabList, boolean quickMode) {
-        return resetWithTabs(PseudoTab.getListOfPseudoTab(tabList), quickMode);
+        return resetWithTabs(TabModelUtils.convertTabListToListOfTabs(tabList), quickMode);
     }
 
     @Override
-    public boolean resetWithTabs(@Nullable List<PseudoTab> tabs, boolean quickMode) {
+    public boolean resetWithTabs(@Nullable List<Tab> tabs, boolean quickMode) {
         mMessageManager.beforeReset();
         boolean showQuickly = mTabListCoordinator.resetWithListOfTabs(tabs, quickMode);
         mMessageManager.afterReset(tabs == null ? 0 : tabs.size());
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManager.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManager.java
index cb4f200..9786236 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManager.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManager.java
@@ -14,6 +14,7 @@
 import org.chromium.base.ValueChangedCallback;
 import org.chromium.base.supplier.LazyOneshotSupplier;
 import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
@@ -119,6 +120,7 @@
     private final @NonNull SnackbarManager mSnackbarManager;
     private final @NonNull ModalDialogManager mModalDialogManager;
     private final @NonNull TabListCoordinator mTabListCoordinator;
+    private final @NonNull Supplier<Boolean> mVisibilitySupplier;
     private final @NonNull LazyOneshotSupplier<TabListEditorController>
             mTabListEditorControllerSupplier;
     private final @NonNull PriceWelcomeMessageReviewActionProvider
@@ -146,6 +148,7 @@
      * @param snackbarManager The {@link SnackbarManager} for the activity.
      * @param modalDialogManager The {@link ModalDialogManager} for the activity.
      * @param tabListCoordinator The {@link TabListCoordinator} to show messages on.
+     * @param visibilitySupplier A supplier for the visibility of the {@link TabListCoordinator}.
      * @param tabListEditorControllerSupplier The supplier of the {@link TabListEditorController}.
      * @param priceWelcomeMessageReviewActionProvider The review action provider for price welcome.
      * @param mode The {@link TabListMode} the {@link TabListCoordinator} is in.
@@ -159,6 +162,7 @@
             @NonNull SnackbarManager snackbarManager,
             @NonNull ModalDialogManager modalDialogManager,
             @NonNull TabListCoordinator tabListCoordinator,
+            @NonNull Supplier<Boolean> visibilitySupplier,
             @NonNull LazyOneshotSupplier<TabListEditorController> tabListEditorControllerSupplier,
             @NonNull
                     PriceWelcomeMessageReviewActionProvider priceWelcomeMessageReviewActionProvider,
@@ -171,6 +175,7 @@
         mSnackbarManager = snackbarManager;
         mModalDialogManager = modalDialogManager;
         mTabListCoordinator = tabListCoordinator;
+        mVisibilitySupplier = visibilitySupplier;
         mTabListEditorControllerSupplier = tabListEditorControllerSupplier;
         mPriceWelcomeMessageReviewActionProvider = priceWelcomeMessageReviewActionProvider;
         mMode = mode;
@@ -339,7 +344,8 @@
     }
 
     private void appendMessagesTo(int index) {
-        if (mMultiWindowModeStateDispatcher.isInMultiWindowMode()) return;
+        if (!shouldShowMessages()) return;
+
         sAppendedMessagesForTesting = false;
         List<MessageCardProviderMediator.Message> messages =
                 mMessageCardProviderCoordinator.getMessageItems();
@@ -350,7 +356,10 @@
                         index, TabProperties.UiType.LARGE_MESSAGE, messages.get(i).model);
             } else if (messages.get(i).type
                     == MessageService.MessageType.INCOGNITO_REAUTH_PROMO_MESSAGE) {
-                mayAddIncognitoReauthPromoCard(messages.get(i).model);
+                if (!mayAddIncognitoReauthPromoCard(messages.get(i).model)) {
+                    // Skip incrementing index if the message was not added.
+                    continue;
+                }
             } else if (messages.get(i).type == MessageService.MessageType.TAB_SUGGESTION) {
                 // TODO(crbug.com/40073668): Update to a mayAdd call checking show criteria
                 mTabListCoordinator.addSpecialListItem(
@@ -369,11 +378,13 @@
         }
     }
 
-    private void mayAddIncognitoReauthPromoCard(PropertyModel model) {
+    private boolean mayAddIncognitoReauthPromoCard(PropertyModel model) {
         if (mIncognitoReauthPromoMessageService.isIncognitoReauthPromoMessageEnabled(mProfile)) {
             mTabListCoordinator.addSpecialListItemToEnd(TabProperties.UiType.LARGE_MESSAGE, model);
             mIncognitoReauthPromoMessageService.increasePromoShowCountAndMayDisableIfCountExceeds();
+            return true;
         }
+        return false;
     }
 
     private boolean shouldAppendMessage(MessageCardProviderMediator.Message message) {
@@ -420,6 +431,8 @@
      * message items when the closure of the last tab in tab switcher is undone.
      */
     private void restoreAllAppendedMessage() {
+        if (!shouldShowMessages()) return;
+
         // TODO(crbug.com/340730009): The Profile should never be null, and this should be removed
         // once this bug is addressed.
         if (mCurrentTabModelFilterSupplier.get().getTabModel().getProfile() == null) return;
@@ -522,6 +535,10 @@
         }
     }
 
+    private boolean shouldShowMessages() {
+        return !mMultiWindowModeStateDispatcher.isInMultiWindowMode() && mVisibilitySupplier.get();
+    }
+
     /** Returns whether this manager has appended any messages. */
     public static boolean hasAppendedMessagesForTesting() {
         return sAppendedMessagesForTesting;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManagerUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManagerUnitTest.java
index fb4c422..b47b0a7 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManagerUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManagerUnitTest.java
@@ -32,6 +32,7 @@
 
 import org.chromium.base.supplier.LazyOneshotSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.base.supplier.Supplier;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
@@ -85,6 +86,8 @@
 
     private final ObservableSupplierImpl<TabModelFilter> mCurrentTabModelFilterSupplier =
             new ObservableSupplierImpl<>();
+    private boolean mVisible;
+    private final Supplier<Boolean> mVisibilitySupplier = () -> mVisible;
 
     private TabSwitcherMessageManager mMessageManager;
     private MockTab mTab1;
@@ -107,6 +110,7 @@
         doReturn(mTabModel).when(mTabModelFilter).getTabModel();
         doReturn(mProfile).when(mTabModel).getProfile();
         mCurrentTabModelFilterSupplier.set(mTabModelFilter);
+        mVisible = true;
 
         mActivityScenarioRule.getScenario().onActivity(this::onActivityReady);
     }
@@ -124,6 +128,7 @@
                         mSnackbarManager,
                         mModalDialogManager,
                         mTabListCoordinator,
+                        mVisibilitySupplier,
                         LazyOneshotSupplier.fromValue(mTabListEditorController),
                         mPriceWelcomeMessageReviewActionProvider,
                         TabListMode.GRID);
@@ -159,6 +164,13 @@
         mMessageManager.afterReset(1);
         verify(mMessageUpdateObserver, times(2)).onRemoveAllAppendedMessage();
         verify(mMessageUpdateObserver).onAppendedMessage();
+
+        mVisible = false;
+
+        mMessageManager.afterReset(1);
+        verify(mMessageUpdateObserver, times(3)).onRemoveAllAppendedMessage();
+        // Not incremented a second time.
+        verify(mMessageUpdateObserver).onAppendedMessage();
     }
 
     @Test
@@ -230,6 +242,15 @@
 
     @Test
     @SmallTest
+    public void exitMultiWindowMode_NotVisible() {
+        mVisible = false;
+        mMultiWindowModeObserverCaptor.getValue().onMultiWindowModeChanged(false);
+
+        verify(mMessageUpdateObserver, never()).onRestoreAllAppendedMessage();
+    }
+
+    @Test
+    @SmallTest
     public void removePriceWelcomeMessageWhenCloseBindingTab() {
         doReturn(1).when(mTabModel).getCount();
         doReturn(TAB1_ID).when(mPriceMessageService).getBindingTabId();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
index a44cf4ad..3dea7cc2 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
@@ -45,7 +45,6 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab_ui.RecyclerViewPosition;
 import org.chromium.chrome.browser.tab_ui.TabSwitcherCustomViewManager;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.components.browser_ui.styles.ChromeColors;
@@ -322,7 +321,7 @@
     }
 
     @Override
-    public boolean resetWithTabs(@Nullable List<PseudoTab> tabs, boolean quickMode) {
+    public boolean resetWithTabs(@Nullable List<Tab> tabs, boolean quickMode) {
         assert false : "Not reached.";
         return true;
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinator.java
index 856ed675..a7f3999 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinator.java
@@ -42,8 +42,7 @@
 import org.chromium.chrome.browser.tabmodel.TabList;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab.TitleProvider;
+import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.TabGridDialogMediator.DialogController;
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
@@ -92,7 +91,6 @@
      * @param regularTabModelSupplier The supplier of the regular tab model.
      * @param tabContentManager For management of thumbnails.
      * @param tabCreatorManager For creating new tabs.
-     * @param titleProvider The default title provider for tabs and tab groups.
      * @param browserControlsStateProvider For determining thumbnail size.
      * @param multiWindowModeStateDispatcher For managing behavior in multi-window.
      * @param scrimCoordinator The scrim coordinator to use for the tab grid dialog.
@@ -115,7 +113,6 @@
             @NonNull Supplier<TabModel> regularTabModelSupplier,
             @NonNull TabContentManager tabContentManager,
             @NonNull TabCreatorManager tabCreatorManager,
-            @NonNull TitleProvider titleProvider,
             @NonNull BrowserControlsStateProvider browserControlsStateProvider,
             @NonNull MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
             @NonNull ScrimCoordinator scrimCoordinator,
@@ -209,7 +206,6 @@
                             tabModelFilterSupplier,
                             regularTabModelSupplier,
                             mMultiThumbnailCardProvider,
-                            titleProvider,
                             /* actionOnRelatedTabs= */ true,
                             getGridCardOnClickListenerProvider(),
                             /* dialogHandler= */ null,
@@ -273,6 +269,7 @@
                             snackbarManager,
                             modalDialogManager,
                             tabListCoordinator,
+                            isVisibleSupplier,
                             tabListEditorControllerSupplier,
                             /* priceWelcomeMessageReviewActionProvider= */ mMediator,
                             mode);
@@ -319,13 +316,14 @@
      * @param tabList The {@link TabList} to show tabs for.
      */
     public void resetWithTabList(@Nullable TabList tabList) {
-        var pseudoTabList = PseudoTab.getListOfPseudoTab(tabList);
+        List<Tab> tabs = TabModelUtils.convertTabListToListOfTabs(tabList);
         mMessageManager.beforeReset();
         // Quick mode being false here ensures the selected tab's thumbnail gets updated. With Hub
         // the TabListCoordinator no longer triggers thumbnail captures so this shouldn't guard
         // against the large amount of work that is used to.
-        mTabListCoordinator.resetWithListOfTabs(pseudoTabList, /* quickMode= */ false);
-        mMessageManager.afterReset(pseudoTabList == null ? 0 : pseudoTabList.size());
+        mTabListCoordinator.resetWithListOfTabs(
+                tabList == null ? null : tabs, /* quickMode= */ false);
+        mMessageManager.afterReset(tabs.size());
     }
 
     /** Performs soft cleanup which removes thumbnails to relieve memory usage. */
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java
index d5a4c94..5f5c78c 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.tasks.tab_management;
 
 import android.app.Activity;
-import android.content.Context;
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
@@ -19,17 +18,12 @@
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
 import org.chromium.chrome.browser.profiles.ProfileProvider;
-import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab_ui.TabContentManager;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
-import org.chromium.chrome.browser.tabmodel.TabModelUtils;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab.TitleProvider;
-import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.tab_ui.R;
@@ -41,7 +35,6 @@
 
 /** Holds dependencies for constructing a {@link TabSwitcherPane}. */
 public class TabSwitcherPaneCoordinatorFactory {
-    private final TitleProvider mTitleProvider = this::getTitle;
     private final Activity mActivity;
     private final ActivityLifecycleDispatcher mLifecycleDispatcher;
     private final OneshotSupplier<ProfileProvider> mProfileProviderSupplier;
@@ -132,7 +125,6 @@
                 () -> mTabModelSelector.getModel(false),
                 mTabContentManager,
                 mTabCreatorManager,
-                mTitleProvider,
                 mBrowserControlsStateProvider,
                 mMultiWindowModeStateDispatcher,
                 mScrimCoordinator,
@@ -157,23 +149,6 @@
         return mMode;
     }
 
-    /** Returns the title of a tab or tab group for display in the tab switcher. */
-    @VisibleForTesting
-    String getTitle(@NonNull Context context, @NonNull PseudoTab pseudoTab) {
-        assert mTabModelSelector.isTabStateInitialized();
-        TabGroupModelFilter filter =
-                (TabGroupModelFilter)
-                        mTabModelSelector
-                                .getTabModelFilterProvider()
-                                .getTabModelFilter(pseudoTab.isIncognito());
-        Tab tab = TabModelUtils.getTabById(filter.getTabModel(), pseudoTab.getId());
-        assert tab != null;
-        if (!filter.isTabInTabGroup(tab)) return tab.getTitle();
-
-        return TabGroupTitleEditor.getDefaultTitle(
-                context, filter.getRelatedTabCountForRootId(tab.getRootId()));
-    }
-
     /** Returns a scrim coordinator to use for tab grid dialog on LFF devices. */
     @VisibleForTesting
     static ScrimCoordinator createScrimCoordinatorForTablet(Activity activity) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java
index 4cb5014..c58dd97 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java
@@ -45,7 +45,6 @@
 import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
@@ -166,30 +165,6 @@
 
     @Test
     @SmallTest
-    public void testGetTitle_Tab() {
-        when(mTabModelSelector.isTabStateInitialized()).thenReturn(true);
-        when(mTabModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
-
-        PseudoTab tab1 = PseudoTab.fromTab(mTab1);
-        assertEquals(TAB1_TITLE, mFactory.getTitle(mActivity, tab1));
-    }
-
-    @Test
-    @SmallTest
-    public void testGetTitle_TabGroup() {
-        when(mTabModelSelector.isTabStateInitialized()).thenReturn(true);
-        when(mTabModelFilter.isTabInTabGroup(mTab1)).thenReturn(true);
-        int tabCount = 2;
-        when(mTabModelFilter.getRelatedTabCountForRootId(TAB1_ID)).thenReturn(tabCount);
-
-        PseudoTab tab1 = PseudoTab.fromTab(mTab1);
-        assertEquals(
-                TabGroupTitleEditor.getDefaultTitle(mActivity, tabCount),
-                mFactory.getTitle(mActivity, tab1));
-    }
-
-    @Test
-    @SmallTest
     public void testCreateScrimCoordinatorForTablet() {
         assertNotNull(TabSwitcherPaneCoordinatorFactory.createScrimCoordinatorForTablet(mActivity));
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
index d69ec9a..26dff8b 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
@@ -65,7 +65,6 @@
 import org.chromium.chrome.browser.tab_ui.TabThumbnailView;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab.TitleProvider;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.TabGridDialogMediator.DialogController;
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
@@ -106,7 +105,6 @@
     @Mock private TabGroupModelFilter mTabModelFilter;
     @Mock private TabContentManager mTabContentManager;
     @Mock private TabCreatorManager mTabCreatorManager;
-    @Mock private TitleProvider mTitleProvider;
     @Mock private BrowserControlsStateProvider mBrowserControlsStateProvider;
     @Mock private MultiWindowModeStateDispatcher mMultiWindowModeStateDispatcher;
     @Mock private ScrimCoordinator mScrimCoordinator;
@@ -181,7 +179,6 @@
                         () -> mTabModel,
                         mTabContentManager,
                         mTabCreatorManager,
-                        mTitleProvider,
                         mBrowserControlsStateProvider,
                         mMultiWindowModeStateDispatcher,
                         mScrimCoordinator,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherResetHandler.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherResetHandler.java
index ad015a0..4befde2 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherResetHandler.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherResetHandler.java
@@ -6,8 +6,8 @@
 
 import androidx.annotation.Nullable;
 
+import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabList;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 
 import java.util.List;
 
@@ -23,15 +23,15 @@
     boolean resetWithTabList(@Nullable TabList tabList, boolean quickMode);
 
     /**
-     * Reset the tab grid with the given {@link List<PseudoTab>}, which can be null.
+     * Reset the tab grid with the given {@link List<Tab>}, which can be null.
      *
-     * @param tabs The {@link List<PseudoTab>} to show the tabs for in the grid.
+     * @param tabs The {@link List<Tab>} to show the tabs for in the grid.
      * @param quickMode Whether to skip capturing the selected live tab for the thumbnail.
      * @return Whether the {@link TabListRecyclerView} can be shown quickly.
      * @deprecated Use resetWithTabList instead to minimize the surface area of PseudoTab which
      *     should be removed with instant start. See https://crbug.com/1413207.
      */
-    boolean resetWithTabs(@Nullable List<PseudoTab> tabs, boolean quickMode);
+    boolean resetWithTabs(@Nullable List<Tab> tabs, boolean quickMode);
 
     /**
      * Release the thumbnail {@link Bitmap} but keep the {@link TabGridView}.
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
index 22452e9..c3ddf94 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogTest.java
@@ -138,7 +138,6 @@
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.chrome.browser.tasks.pseudotab.TabAttributeCache;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
@@ -1900,9 +1899,7 @@
         finishActivity(sActivityTestRule.getActivity());
         createThumbnailBitmapAndWriteToFile(0, mBrowserControlsStateProvider);
         createThumbnailBitmapAndWriteToFile(1, mBrowserControlsStateProvider);
-        TabAttributeCache.setRootIdForTesting(0, 0);
-        TabAttributeCache.setRootIdForTesting(1, 0);
-        createTabStatesAndMetadataFile(new int[] {0, 1});
+        createTabStatesAndMetadataFile(new int[] {0, 1}, new int[] {0, 0});
 
         // Restart Chrome and make sure tab strip is showing.
         sActivityTestRule.startMainActivityFromLauncher();
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiTest.java
index 79d1a99e..ba3336a 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiTest.java
@@ -67,7 +67,6 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModel;
-import org.chromium.chrome.browser.tasks.pseudotab.TabAttributeCache;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.toolbar.bottom.BottomControlsCoordinator;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -292,9 +291,7 @@
         finishActivity(sActivityTestRule.getActivity());
         createThumbnailBitmapAndWriteToFile(0, mBrowserControlsStateProvider);
         createThumbnailBitmapAndWriteToFile(1, mBrowserControlsStateProvider);
-        TabAttributeCache.setRootIdForTesting(0, 0);
-        TabAttributeCache.setRootIdForTesting(1, 0);
-        createTabStatesAndMetadataFile(new int[] {0, 1});
+        createTabStatesAndMetadataFile(new int[] {0, 1}, new int[] {0, 0});
 
         // Restart Chrome and make sure tab strip is showing.
         sActivityTestRule.startMainActivityFromLauncher();
@@ -336,9 +333,7 @@
         finishActivity(sActivityTestRule.getActivity());
         createThumbnailBitmapAndWriteToFile(0, mBrowserControlsStateProvider);
         createThumbnailBitmapAndWriteToFile(1, mBrowserControlsStateProvider);
-        TabAttributeCache.setRootIdForTesting(0, 0);
-        TabAttributeCache.setRootIdForTesting(1, 0);
-        createTabStatesAndMetadataFile(new int[] {0, 1});
+        createTabStatesAndMetadataFile(new int[] {0, 1}, new int[] {0, 0});
 
         // Restart Chrome and make sure both tab strip and IPH text bubble are showing.
         sActivityTestRule.startMainActivityFromLauncher();
@@ -406,9 +401,7 @@
         finishActivity(sActivityTestRule.getActivity());
         createThumbnailBitmapAndWriteToFile(0, mBrowserControlsStateProvider);
         createThumbnailBitmapAndWriteToFile(1, mBrowserControlsStateProvider);
-        TabAttributeCache.setRootIdForTesting(0, 0);
-        TabAttributeCache.setRootIdForTesting(1, 0);
-        createTabStatesAndMetadataFile(new int[] {0, 1});
+        createTabStatesAndMetadataFile(new int[] {0, 1}, new int[] {0, 0});
 
         // Restart Chrome and make sure tab strip is showing.
         sActivityTestRule.startMainActivityFromLauncher();
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
index b676e54..47594e7 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabUiTestHelper.java
@@ -79,7 +79,6 @@
 import org.chromium.chrome.browser.tab_ui.TabThumbnailView;
 import org.chromium.chrome.browser.tab_ui.TabUiThemeUtils;
 import org.chromium.chrome.browser.tabmodel.TabModel;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
@@ -742,7 +741,6 @@
     /** Finishes the given activity and do tab_ui-specific cleanup. */
     public static void finishActivity(final Activity activity) throws Exception {
         ApplicationTestUtils.finishActivity(activity);
-        PseudoTab.clearForTesting();
     }
 
     /**
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java
deleted file mode 100644
index 542f350..0000000
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java
+++ /dev/null
@@ -1,439 +0,0 @@
-// Copyright 2019 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.tasks.pseudotab;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-
-import org.junit.After;
-import org.junit.Assert;
-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.MockitoAnnotations;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.Token;
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.Features;
-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.browser.tabmodel.TabList;
-import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
-import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.chrome.browser.tasks.tab_management.TabUiUnitTestUtils;
-import org.chromium.url.GURL;
-import org.chromium.url.JUnitTestGURLs;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.IntStream;
-
-/** Unit tests for {@link PseudoTab}. */
-@SuppressWarnings({"ResultOfMethodCallIgnored", "deprecation"})
-@RunWith(BaseRobolectricTestRunner.class)
-public class PseudoTabUnitTest {
-    @Rule public TestRule mProcessor = new Features.JUnitProcessor();
-
-    private static final int TAB1_ID = 456;
-    private static final int TAB2_ID = 789;
-    private static final int TAB3_ID = 123;
-    private static final int TAB4_ID = 159;
-
-    @Mock TabModelFilter mTabModelFilter;
-    @Mock TabModelFilter mTabModelFilter2;
-    @Mock TabModelSelector mTabModelSelector;
-    @Mock TabModelFilterProvider mTabModelFilterProvider;
-    @Mock Profile mProfile;
-
-    private Tab mTab1;
-    private Tab mTab2;
-    private Tab mTab3;
-    private Tab mTab1Copy;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mTab1 = TabUiUnitTestUtils.prepareTab(TAB1_ID);
-        mTab2 = TabUiUnitTestUtils.prepareTab(TAB2_ID);
-        mTab3 = TabUiUnitTestUtils.prepareTab(TAB3_ID);
-        mTab1Copy = TabUiUnitTestUtils.prepareTab(TAB1_ID);
-
-        doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider();
-    }
-
-    @After
-    public void tearDown() {
-        PseudoTab.clearForTesting();
-    }
-
-    @Test
-    public void fromTabId() {
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(TAB1_ID, tab.getId());
-        Assert.assertFalse(tab.hasRealTab());
-        Assert.assertNull(tab.getTab());
-    }
-
-    @Test
-    public void fromTabId_cached() {
-        PseudoTab tab1 = PseudoTab.fromTabId(TAB1_ID);
-        PseudoTab tab2 = PseudoTab.fromTabId(TAB2_ID);
-        PseudoTab tab1prime = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertNotEquals(tab1, tab2);
-        Assert.assertEquals(tab1, tab1prime);
-    }
-
-    @Test
-    public void fromTabId_threadSafety() throws InterruptedException {
-        final int count = 100000;
-        ExecutorService service = Executors.newCachedThreadPool();
-
-        IntStream.range(0, count)
-                .forEach(tabId -> service.submit(() -> PseudoTab.fromTabId(tabId)));
-        service.awaitTermination(1000, TimeUnit.MILLISECONDS);
-
-        Assert.assertEquals(count, PseudoTab.getAllTabsCountForTests());
-    }
-
-    @Test
-    public void fromTab() {
-        PseudoTab tab = PseudoTab.fromTab(mTab1);
-        Assert.assertEquals(TAB1_ID, tab.getId());
-        Assert.assertTrue(tab.hasRealTab());
-        Assert.assertEquals(mTab1, tab.getTab());
-    }
-
-    @Test
-    public void fromTab_cached() {
-        PseudoTab tab1 = PseudoTab.fromTab(mTab1);
-        PseudoTab tab2 = PseudoTab.fromTab(mTab2);
-        PseudoTab tab1prime = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab1, tab2);
-        Assert.assertEquals(tab1, tab1prime);
-    }
-
-    @Test
-    public void fromTab_obsoleteCache() {
-        PseudoTab tab1 = PseudoTab.fromTab(mTab1);
-        PseudoTab tab1copy = PseudoTab.fromTab(mTab1Copy);
-        Assert.assertNotEquals(tab1, tab1copy);
-        Assert.assertEquals(tab1.getId(), tab1copy.getId());
-    }
-
-    @Test
-    public void fromTab_cached_upgrade() {
-        PseudoTab tab1 = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertFalse(tab1.hasRealTab());
-
-        PseudoTab tab1upgraded = PseudoTab.fromTab(mTab1);
-        Assert.assertTrue(tab1upgraded.hasRealTab());
-
-        Assert.assertNotEquals(tab1, tab1upgraded);
-
-        PseudoTab tab1prime = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(tab1upgraded, tab1prime);
-    }
-
-    @Test
-    public void getListOfPseudoTab_listOfTab() {
-        List<PseudoTab> list = PseudoTab.getListOfPseudoTab(Arrays.asList(mTab1, mTab2));
-        Assert.assertEquals(2, list.size());
-        Assert.assertEquals(TAB1_ID, list.get(0).getId());
-        Assert.assertEquals(TAB2_ID, list.get(1).getId());
-    }
-
-    @Test
-    public void getListOfPseudoTab_listOfTab_null() {
-        List<PseudoTab> list = PseudoTab.getListOfPseudoTab((List<Tab>) null);
-        Assert.assertNull(list);
-    }
-
-    @Test
-    public void getListOfPseudoTab_TabList() {
-        doReturn(mTab1).when(mTabModelFilter).getTabAt(0);
-        doReturn(mTab2).when(mTabModelFilter).getTabAt(1);
-        doReturn(mTab3).when(mTabModelFilter).getTabAt(2);
-        doReturn(3).when(mTabModelFilter).getCount();
-
-        List<PseudoTab> list = PseudoTab.getListOfPseudoTab(mTabModelFilter);
-        Assert.assertEquals(3, list.size());
-        Assert.assertEquals(TAB1_ID, list.get(0).getId());
-        Assert.assertEquals(TAB2_ID, list.get(1).getId());
-        Assert.assertEquals(TAB3_ID, list.get(2).getId());
-    }
-
-    @Test
-    public void getListOfPseudoTab_TabList_null() {
-        List<PseudoTab> list = PseudoTab.getListOfPseudoTab((TabList) null);
-        Assert.assertNull(list);
-    }
-
-    @Test
-    public void testToString() {
-        Assert.assertEquals("Tab 456", PseudoTab.fromTabId(TAB1_ID).toString());
-    }
-
-    @Test
-    public void getTitle_provider() {
-        String title = "title provider";
-        PseudoTab.TitleProvider provider = (context, tab) -> title;
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(title, tab.getTitle(ContextUtils.getApplicationContext(), provider));
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertEquals(
-                title, realTab.getTitle(ContextUtils.getApplicationContext(), provider));
-    }
-
-    @Test
-    public void getTitle_nullProvider() {
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(
-                tab.getTitle(), tab.getTitle(ContextUtils.getApplicationContext(), null));
-    }
-
-    @Test
-    public void getTitle_realTab() {
-        String title = "title 1 real";
-        doReturn(title).when(mTab1).getTitle();
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals("", tab.getTitle());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertEquals(title, realTab.getTitle());
-    }
-
-    @Test
-    public void getTitle_cache() {
-        String title = "title 1";
-        TabAttributeCache.setTitleForTesting(TAB1_ID, title);
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(title, tab.getTitle());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertNull(realTab.getTitle());
-    }
-
-    @Test
-    public void getUrl_real() {
-        GURL url = JUnitTestGURLs.EXAMPLE_URL;
-        doReturn(url).when(mTab1).getUrl();
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(GURL.emptyGURL(), tab.getUrl());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertEquals(url, realTab.getUrl());
-    }
-
-    @Test
-    public void getUrl_cache() {
-        TabAttributeCache.setUrlForTesting(TAB1_ID, JUnitTestGURLs.URL_1);
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(JUnitTestGURLs.URL_1.getSpec(), tab.getUrl().getSpec());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertNull(realTab.getUrl());
-    }
-
-    @Test
-    public void getRootId_real() {
-        int rootId = 1337;
-        doReturn(rootId).when(mTab1).getRootId();
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(Tab.INVALID_TAB_ID, tab.getRootId());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertEquals(rootId, realTab.getRootId());
-    }
-
-    @Test
-    public void getRootId_cache() {
-        int rootId = 42;
-        TabAttributeCache.setRootIdForTesting(TAB1_ID, rootId);
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(rootId, tab.getRootId());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertNotEquals(rootId, realTab.getRootId());
-    }
-
-    @Test
-    public void getTabGroupId_real() {
-        Token tabGroupId = new Token(123L, 456L);
-        doReturn(tabGroupId).when(mTab1).getTabGroupId();
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(null, tab.getTabGroupId());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertEquals(tabGroupId, realTab.getTabGroupId());
-    }
-
-    @Test
-    public void getTabGroupId_cache() {
-        Token tabGroupId = new Token(1L, 4L);
-        TabAttributeCache.setTabGroupIdForTesting(TAB1_ID, tabGroupId);
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(tabGroupId, tab.getTabGroupId());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertNotEquals(tabGroupId, realTab.getTabGroupId());
-    }
-
-    @Test
-    public void getTimestampMillis_real() {
-        long timestamp = 12345;
-        doReturn(timestamp).when(mTab1).getTimestampMillis();
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(Tab.INVALID_TIMESTAMP, tab.getTimestampMillis());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertEquals(timestamp, realTab.getTimestampMillis());
-    }
-
-    @Test
-    public void getTimestampMillis_cache() {
-        long timestamp = 42;
-        TabAttributeCache.setTimestampMillisForTesting(TAB1_ID, timestamp);
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertEquals(timestamp, tab.getTimestampMillis());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertNotEquals(timestamp, realTab.getTimestampMillis());
-    }
-
-    @Test
-    public void isIncognito() {
-        doReturn(true).when(mTab1).isIncognito();
-
-        PseudoTab tab = PseudoTab.fromTabId(TAB1_ID);
-        Assert.assertFalse(tab.isIncognito());
-
-        PseudoTab realTab = PseudoTab.fromTab(mTab1);
-        Assert.assertNotEquals(tab, realTab);
-        Assert.assertTrue(realTab.isIncognito());
-
-        doReturn(false).when(mTab1).isIncognito();
-        Assert.assertFalse(realTab.isIncognito());
-    }
-
-    @Test
-    public void getRelatedTabs_provider_normal() {
-        doReturn(true).when(mTabModelSelector).isTabStateInitialized();
-        doReturn(mTabModelFilter).when(mTabModelFilterProvider).getTabModelFilter(eq(false));
-        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3));
-        doReturn(tabs).when(mTabModelFilter).getRelatedTabList(TAB1_ID);
-
-        PseudoTab tab1 = PseudoTab.fromTabId(TAB1_ID);
-        List<PseudoTab> related =
-                PseudoTab.getRelatedTabs(
-                        ContextUtils.getApplicationContext(), tab1, mTabModelSelector);
-        Assert.assertEquals(3, related.size());
-        Assert.assertEquals(TAB1_ID, related.get(0).getId());
-        Assert.assertEquals(TAB2_ID, related.get(1).getId());
-        Assert.assertEquals(TAB3_ID, related.get(2).getId());
-    }
-
-    @Test
-    public void getRelatedTabs_provider_incognito() {
-        doReturn(true).when(mTabModelSelector).isTabStateInitialized();
-        doReturn(mTabModelFilter).when(mTabModelFilterProvider).getTabModelFilter(eq(false));
-        List<Tab> empty = new ArrayList<>();
-        doReturn(empty).when(mTabModelFilter).getRelatedTabList(TAB1_ID);
-        doReturn(mTabModelFilter2).when(mTabModelFilterProvider).getTabModelFilter(eq(true));
-        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
-        doReturn(tabs).when(mTabModelFilter2).getRelatedTabList(TAB1_ID);
-
-        PseudoTab tab1 = PseudoTab.fromTabId(TAB1_ID);
-        List<PseudoTab> related =
-                PseudoTab.getRelatedTabs(
-                        ContextUtils.getApplicationContext(), tab1, mTabModelSelector);
-        Assert.assertEquals(2, related.size());
-        Assert.assertEquals(TAB1_ID, related.get(0).getId());
-        Assert.assertEquals(TAB2_ID, related.get(1).getId());
-    }
-
-    @Test
-    public void testTabDestroyedTitle() {
-        Tab tab = new MockTab(TAB4_ID, mProfile);
-        PseudoTab pseudoTab = PseudoTab.fromTab(tab);
-        tab.destroy();
-        // Title was not set. Without the isInitialized() check,
-        // pseudoTab.getTitle() would crash here with UnsupportedOperationException
-        Assert.assertEquals("", pseudoTab.getTitle());
-    }
-
-    @Test
-    public void testTabDestroyedUrl() {
-        Tab tab = new MockTab(TAB4_ID, mProfile);
-        PseudoTab pseudoTab = PseudoTab.fromTab(tab);
-        tab.destroy();
-        // Url was not set. Without the isInitialized() check,
-        // pseudoTab.getUrl() would crash here with UnsupportedOperationException
-        Assert.assertEquals("", pseudoTab.getUrl().getSpec());
-    }
-
-    @Test
-    public void testTabDestroyedRootId() {
-        Tab tab = new MockTab(TAB4_ID, mProfile);
-        PseudoTab pseudoTab = PseudoTab.fromTab(tab);
-        tab.destroy();
-        // Root ID was not set. Without the isInitialized() check,
-        // pseudoTab.getRootId() would crash here with UnsupportedOperationException
-        Assert.assertEquals(Tab.INVALID_TAB_ID, pseudoTab.getRootId());
-    }
-
-    @Test
-    public void testTabDestroyedTimestamp() {
-        Tab tab = new MockTab(TAB4_ID, mProfile);
-        PseudoTab pseudoTab = PseudoTab.fromTab(tab);
-        tab.destroy();
-        // Timestamp was not set. Without the isInitialized() check,
-        // pseudoTab.getTimestampMillis() would crash here with
-        // UnsupportedOperationException
-        Assert.assertEquals(Tab.INVALID_TIMESTAMP, pseudoTab.getTimestampMillis());
-    }
-
-    @Test
-    public void testTabIsClosingOrDestroyed_Destroyed() {
-        Tab tab = new MockTab(TAB4_ID, mProfile);
-        PseudoTab pseudoTab = PseudoTab.fromTab(tab);
-        Assert.assertFalse(pseudoTab.isClosingOrDestroyed());
-        tab.destroy();
-        Assert.assertTrue(pseudoTab.isClosingOrDestroyed());
-    }
-}
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCacheUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCacheUnitTest.java
deleted file mode 100644
index 787cceb..0000000
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCacheUnitTest.java
+++ /dev/null
@@ -1,438 +0,0 @@
-// Copyright 2019 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.tasks.pseudotab;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.junit.After;
-import org.junit.Assert;
-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.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
-
-import org.chromium.base.Token;
-import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.Features;
-import org.chromium.base.test.util.JniMocker;
-import org.chromium.chrome.browser.profiles.Profile;
-import org.chromium.chrome.browser.profiles.ProfileJni;
-import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tabmodel.TabModel;
-import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
-import org.chromium.chrome.browser.tabmodel.TabModelObserver;
-import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
-import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
-import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
-import org.chromium.chrome.browser.tasks.pseudotab.TabAttributeCache.LastSearchTermProvider;
-import org.chromium.chrome.browser.tasks.tab_management.TabUiUnitTestUtils;
-import org.chromium.components.search_engines.TemplateUrlService;
-import org.chromium.content_public.browser.NavigationController;
-import org.chromium.content_public.browser.NavigationEntry;
-import org.chromium.content_public.browser.NavigationHandle;
-import org.chromium.content_public.browser.NavigationHistory;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.url.GURL;
-import org.chromium.url.JUnitTestGURLs;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** Unit tests for {@link TabAttributeCache}. */
-@SuppressWarnings("ResultOfMethodCallIgnored")
-@RunWith(BaseRobolectricTestRunner.class)
-@Config(manifest = Config.NONE)
-public class TabAttributeCacheUnitTest {
-    @Rule public TestRule mProcessor = new Features.JUnitProcessor();
-    @Rule public JniMocker jniMocker = new JniMocker();
-
-    private static final int TAB1_ID = 456;
-    private static final int TAB2_ID = 789;
-    private static final int POSITION1 = 0;
-    private static final int POSITION2 = 1;
-
-    @Mock TabModelSelectorImpl mTabModelSelector;
-    @Mock TabModelFilterProvider mTabModelFilterProvider;
-    @Mock TabModelFilter mTabModelFilter;
-    @Mock TabModel mTabModel;
-    @Captor ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
-    @Captor ArgumentCaptor<TabModelSelectorObserver> mTabModelSelectorObserverCaptor;
-    @Captor ArgumentCaptor<TabModelSelectorTabObserver> mTabObserverCaptor;
-    @Mock Profile.Natives mProfileJniMock;
-
-    private Tab mTab1;
-    private Tab mTab2;
-    private TabAttributeCache mCache;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        jniMocker.mock(ProfileJni.TEST_HOOKS, mProfileJniMock);
-
-        mTab1 = TabUiUnitTestUtils.prepareTab(TAB1_ID);
-        mTab2 = TabUiUnitTestUtils.prepareTab(TAB2_ID);
-
-        List<TabModel> tabModelList = new ArrayList<>();
-        tabModelList.add(mTabModel);
-
-        doReturn(tabModelList).when(mTabModelSelector).getModels();
-        doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider();
-        doReturn(mTabModelFilter).when(mTabModelFilterProvider).getTabModelFilter(eq(false));
-
-        doNothing().when(mTabModelFilter).addObserver(mTabModelObserverCaptor.capture());
-
-        doNothing().when(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
-
-        doReturn(mTab1).when(mTabModel).getTabAt(POSITION1);
-        doReturn(mTab2).when(mTabModel).getTabAt(POSITION2);
-        doReturn(POSITION1).when(mTabModel).indexOf(mTab1);
-        doReturn(POSITION2).when(mTabModel).indexOf(mTab2);
-        doNothing().when(mTab1).addObserver(mTabObserverCaptor.capture());
-        doNothing().when(mTab2).addObserver(mTabObserverCaptor.capture());
-        doReturn(0).when(mTabModel).index();
-        doReturn(2).when(mTabModel).getCount();
-        doReturn(mTabModel).when(mTabModel).getComprehensiveModel();
-
-        mCache = new TabAttributeCache(mTabModelSelector);
-    }
-
-    @After
-    public void tearDown() {
-        mCache.destroy();
-    }
-
-    @Test
-    public void updateUrl() {
-        GURL url = JUnitTestGURLs.EXAMPLE_URL;
-        doReturn(url).when(mTab1).getUrl();
-
-        Assert.assertNotEquals(url, TabAttributeCache.getUrl(TAB1_ID));
-
-        mTabObserverCaptor.getValue().onUrlUpdated(mTab1);
-        Assert.assertEquals(url, TabAttributeCache.getUrl(TAB1_ID));
-
-        mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
-        Assert.assertNotEquals(url, TabAttributeCache.getUrl(TAB1_ID));
-    }
-
-    @Test
-    public void updateUrl_incognito() {
-        GURL url = JUnitTestGURLs.EXAMPLE_URL;
-        doReturn(url).when(mTab1).getUrl();
-        doReturn(true).when(mTab1).isIncognito();
-
-        mTabObserverCaptor.getValue().onUrlUpdated(mTab1);
-        Assert.assertNotEquals(url.getSpec(), TabAttributeCache.getUrl(TAB1_ID));
-    }
-
-    @Test
-    public void updateTitle() {
-        String title = "title 1";
-        doReturn(title).when(mTab1).getTitle();
-
-        Assert.assertNotEquals(title, TabAttributeCache.getTitle(TAB1_ID));
-
-        mTabObserverCaptor.getValue().onTitleUpdated(mTab1);
-        Assert.assertEquals(title, TabAttributeCache.getTitle(TAB1_ID));
-
-        mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
-        Assert.assertNotEquals(title, TabAttributeCache.getTitle(TAB1_ID));
-    }
-
-    @Test
-    public void updateTitle_incognito() {
-        String title = "title 1";
-        doReturn(title).when(mTab1).getTitle();
-        doReturn(true).when(mTab1).isIncognito();
-
-        mTabObserverCaptor.getValue().onTitleUpdated(mTab1);
-        Assert.assertNotEquals(title, TabAttributeCache.getTitle(TAB1_ID));
-    }
-
-    @Test
-    public void updateRootId() {
-        int rootId = 1337;
-        doReturn(rootId).when(mTab1).getRootId();
-
-        Assert.assertNotEquals(rootId, TabAttributeCache.getRootId(TAB1_ID));
-
-        mTabObserverCaptor.getValue().onRootIdChanged(mTab1, rootId);
-        Assert.assertEquals(rootId, TabAttributeCache.getRootId(TAB1_ID));
-
-        mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
-        Assert.assertNotEquals(rootId, TabAttributeCache.getRootId(TAB1_ID));
-    }
-
-    @Test
-    public void updateRootId_incognito() {
-        int rootId = 1337;
-        doReturn(rootId).when(mTab1).getRootId();
-        doReturn(true).when(mTab1).isIncognito();
-
-        mTabObserverCaptor.getValue().onRootIdChanged(mTab1, rootId);
-        Assert.assertNotEquals(rootId, TabAttributeCache.getRootId(TAB1_ID));
-    }
-
-    @Test
-    public void updateTabGroupId() {
-        Token tabGroupId = new Token(0x1337L, 0xBADL);
-        doReturn(tabGroupId).when(mTab1).getTabGroupId();
-
-        Assert.assertNull(TabAttributeCache.getTabGroupId(TAB1_ID));
-
-        // 1. Set token.
-        mTabObserverCaptor.getValue().onTabGroupIdChanged(mTab1, tabGroupId);
-        Assert.assertEquals(tabGroupId, TabAttributeCache.getTabGroupId(TAB1_ID));
-
-        // 2. Null token.
-        doReturn(null).when(mTab1).getTabGroupId();
-        mTabObserverCaptor.getValue().onTabGroupIdChanged(mTab1, null);
-        Assert.assertNull(TabAttributeCache.getTabGroupId(TAB1_ID));
-
-        // 3. Set token.
-        doReturn(tabGroupId).when(mTab1).getTabGroupId();
-        mTabObserverCaptor.getValue().onTabGroupIdChanged(mTab1, tabGroupId);
-        Assert.assertEquals(tabGroupId, TabAttributeCache.getTabGroupId(TAB1_ID));
-
-        // 4. Delete on close tab.
-        mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
-        Assert.assertNull(TabAttributeCache.getTabGroupId(TAB1_ID));
-    }
-
-    @Test
-    public void updateTabGroupId_incognito() {
-        Token tabGroupId = new Token(0x1337L, 0xBADL);
-        doReturn(tabGroupId).when(mTab1).getTabGroupId();
-        doReturn(true).when(mTab1).isIncognito();
-
-        mTabObserverCaptor.getValue().onTabGroupIdChanged(mTab1, tabGroupId);
-        Assert.assertNull(TabAttributeCache.getTabGroupId(TAB1_ID));
-    }
-
-    @Test
-    public void updateTimestamp() {
-        long timestamp = 1337;
-        doReturn(timestamp).when(mTab1).getTimestampMillis();
-
-        Assert.assertNotEquals(timestamp, TabAttributeCache.getTimestampMillis(TAB1_ID));
-
-        mTabObserverCaptor.getValue().onTimestampChanged(mTab1, timestamp);
-        Assert.assertEquals(timestamp, TabAttributeCache.getTimestampMillis(TAB1_ID));
-
-        mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
-        Assert.assertNotEquals(timestamp, TabAttributeCache.getTimestampMillis(TAB1_ID));
-    }
-
-    @Test
-    public void updateTimestamp_incognito() {
-        long timestamp = 1337;
-        doReturn(timestamp).when(mTab1).getTimestampMillis();
-        doReturn(true).when(mTab1).isIncognito();
-
-        mTabObserverCaptor.getValue().onTimestampChanged(mTab1, timestamp);
-        Assert.assertNotEquals(timestamp, TabAttributeCache.getTimestampMillis(TAB1_ID));
-    }
-
-    @Test
-    public void updateLastSearchTerm() {
-        String searchTerm = "chromium";
-
-        LastSearchTermProvider lastSearchTermProvider = mock(LastSearchTermProvider.class);
-        TabAttributeCache.setLastSearchTermMockForTesting(lastSearchTermProvider);
-        NavigationHandle navigationHandle = mock(NavigationHandle.class);
-
-        Assert.assertNull(TabAttributeCache.getLastSearchTerm(TAB1_ID));
-
-        doReturn(searchTerm).when(lastSearchTermProvider).getLastSearchTerm(mTab1);
-        WebContents webContents = mock(WebContents.class);
-        doReturn(webContents).when(mTab1).getWebContents();
-
-        mTabObserverCaptor
-                .getValue()
-                .onDidFinishNavigationInPrimaryMainFrame(mTab1, navigationHandle);
-        Assert.assertEquals(searchTerm, TabAttributeCache.getLastSearchTerm(TAB1_ID));
-
-        // Empty strings should propagate.
-        doReturn("").when(lastSearchTermProvider).getLastSearchTerm(mTab1);
-        mTabObserverCaptor
-                .getValue()
-                .onDidFinishNavigationInPrimaryMainFrame(mTab1, navigationHandle);
-        Assert.assertEquals("", TabAttributeCache.getLastSearchTerm(TAB1_ID));
-
-        // Null should also propagate.
-        doReturn(null).when(lastSearchTermProvider).getLastSearchTerm(mTab1);
-        mTabObserverCaptor
-                .getValue()
-                .onDidFinishNavigationInPrimaryMainFrame(mTab1, navigationHandle);
-        Assert.assertNull(TabAttributeCache.getLastSearchTerm(TAB1_ID));
-
-        mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
-        Assert.assertNull(TabAttributeCache.getLastSearchTerm(TAB1_ID));
-    }
-
-    @Test
-    public void updateLastSearchTerm_incognito() {
-        String searchTerm = "chromium";
-        doReturn(true).when(mTab1).isIncognito();
-
-        LastSearchTermProvider lastSearchTermProvider = mock(LastSearchTermProvider.class);
-        TabAttributeCache.setLastSearchTermMockForTesting(lastSearchTermProvider);
-        doReturn(searchTerm).when(lastSearchTermProvider).getLastSearchTerm(mTab1);
-
-        mTabObserverCaptor.getValue().onDidFinishNavigationInPrimaryMainFrame(mTab1, null);
-        Assert.assertNull(TabAttributeCache.getLastSearchTerm(TAB1_ID));
-    }
-
-    @Test
-    public void findLastSearchTerm() {
-        GURL otherUrl = JUnitTestGURLs.EXAMPLE_URL;
-        GURL searchUrl = JUnitTestGURLs.SEARCH_URL;
-        String searchTerm = "test";
-        GURL searchUrl2 = JUnitTestGURLs.SEARCH_2_URL;
-        String searchTerm2 = "query";
-
-        TemplateUrlService service = Mockito.mock(TemplateUrlService.class);
-        doReturn(null).when(service).getSearchQueryForUrl(otherUrl);
-        doReturn(searchTerm).when(service).getSearchQueryForUrl(searchUrl);
-        doReturn(searchTerm2).when(service).getSearchQueryForUrl(searchUrl2);
-        TemplateUrlServiceFactory.setInstanceForTesting(service);
-
-        WebContents webContents = mock(WebContents.class);
-        doReturn(webContents).when(mTab1).getWebContents();
-        when(mProfileJniMock.fromWebContents(eq(webContents))).thenReturn(mock(Profile.class));
-        NavigationController navigationController = mock(NavigationController.class);
-        doReturn(navigationController).when(webContents).getNavigationController();
-        NavigationHistory navigationHistory = mock(NavigationHistory.class);
-        doReturn(navigationHistory).when(navigationController).getNavigationHistory();
-        doReturn(2).when(navigationHistory).getCurrentEntryIndex();
-        NavigationEntry navigationEntry1 = mock(NavigationEntry.class);
-        NavigationEntry navigationEntry0 = mock(NavigationEntry.class);
-        doReturn(navigationEntry1).when(navigationHistory).getEntryAtIndex(1);
-        doReturn(navigationEntry0).when(navigationHistory).getEntryAtIndex(0);
-        doReturn(otherUrl).when(mTab1).getUrl();
-
-        // No searches.
-        doReturn(otherUrl).when(navigationEntry1).getOriginalUrl();
-        doReturn(otherUrl).when(navigationEntry0).getOriginalUrl();
-        Assert.assertNull(TabAttributeCache.findLastSearchTerm(mTab1));
-
-        // Has SRP.
-        doReturn(searchUrl).when(navigationEntry1).getOriginalUrl();
-        doReturn(otherUrl).when(navigationEntry0).getOriginalUrl();
-        Assert.assertEquals(searchTerm, TabAttributeCache.findLastSearchTerm(mTab1));
-
-        // Has earlier SRP.
-        doReturn(otherUrl).when(navigationEntry1).getOriginalUrl();
-        doReturn(searchUrl2).when(navigationEntry0).getOriginalUrl();
-        Assert.assertEquals(searchTerm2, TabAttributeCache.findLastSearchTerm(mTab1));
-
-        // Latest one wins.
-        doReturn(searchUrl).when(navigationEntry1).getOriginalUrl();
-        doReturn(searchUrl2).when(navigationEntry0).getOriginalUrl();
-        Assert.assertEquals(searchTerm, TabAttributeCache.findLastSearchTerm(mTab1));
-
-        // Only care about previous ones.
-        doReturn(1).when(navigationHistory).getCurrentEntryIndex();
-        Assert.assertEquals(searchTerm2, TabAttributeCache.findLastSearchTerm(mTab1));
-
-        // Skip if the SRP is showing.
-        doReturn(2).when(navigationHistory).getCurrentEntryIndex();
-        doReturn(searchUrl).when(mTab1).getUrl();
-        Assert.assertNull(TabAttributeCache.findLastSearchTerm(mTab1));
-
-        // Reset current SRP.
-        doReturn(otherUrl).when(mTab1).getUrl();
-        Assert.assertEquals(searchTerm, TabAttributeCache.findLastSearchTerm(mTab1));
-
-        verify(navigationHistory, never()).getEntryAtIndex(eq(2));
-    }
-
-    @Test
-    public void removeEscapedCodePoints() {
-        Assert.assertEquals("", TabAttributeCache.removeEscapedCodePoints(""));
-        Assert.assertEquals("", TabAttributeCache.removeEscapedCodePoints("%0a"));
-        Assert.assertEquals("", TabAttributeCache.removeEscapedCodePoints("%0A"));
-        Assert.assertEquals("AB", TabAttributeCache.removeEscapedCodePoints("A%FE%FFB"));
-        Assert.assertEquals("a%0", TabAttributeCache.removeEscapedCodePoints("a%0"));
-        Assert.assertEquals("%", TabAttributeCache.removeEscapedCodePoints("%%00"));
-        Assert.assertEquals("%0G", TabAttributeCache.removeEscapedCodePoints("%0G"));
-        Assert.assertEquals("abcc", TabAttributeCache.removeEscapedCodePoints("abc%abc"));
-        Assert.assertEquals("a%a", TabAttributeCache.removeEscapedCodePoints("a%bc%a%bc"));
-    }
-
-    @Test
-    public void onTabStateInitialized() {
-        GURL url1 = JUnitTestGURLs.EXAMPLE_URL;
-        doReturn(url1).when(mTab1).getUrl();
-        String title1 = "title 1";
-        doReturn(title1).when(mTab1).getTitle();
-        int rootId1 = 1337;
-        doReturn(rootId1).when(mTab1).getRootId();
-        long timestamp1 = 123456;
-        doReturn(timestamp1).when(mTab1).getTimestampMillis();
-
-        GURL url2 = JUnitTestGURLs.URL_2;
-        doReturn(url2).when(mTab2).getUrl();
-        String title2 = "title 2";
-        doReturn(title2).when(mTab2).getTitle();
-        int rootId2 = 42;
-        doReturn(rootId2).when(mTab2).getRootId();
-
-        String searchTerm = "chromium";
-        LastSearchTermProvider lastSearchTermProvider = mock(LastSearchTermProvider.class);
-        TabAttributeCache.setLastSearchTermMockForTesting(lastSearchTermProvider);
-        doReturn(searchTerm).when(lastSearchTermProvider).getLastSearchTerm(mTab1);
-        WebContents webContents = mock(WebContents.class);
-        doReturn(webContents).when(mTab1).getWebContents();
-
-        doReturn(mTab1).when(mTabModelFilter).getTabAt(0);
-        doReturn(mTab2).when(mTabModelFilter).getTabAt(1);
-        doReturn(2).when(mTabModelFilter).getCount();
-        doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
-
-        Assert.assertNotEquals(url1, TabAttributeCache.getUrl(TAB1_ID));
-        Assert.assertNotEquals(title1, TabAttributeCache.getTitle(TAB1_ID));
-        Assert.assertNotEquals(rootId1, TabAttributeCache.getRootId(TAB1_ID));
-        Assert.assertNotEquals(timestamp1, TabAttributeCache.getTimestampMillis(TAB1_ID));
-        Assert.assertNotEquals(searchTerm, TabAttributeCache.getLastSearchTerm(TAB1_ID));
-
-        Assert.assertNotEquals(url2, TabAttributeCache.getUrl(TAB2_ID));
-        Assert.assertNotEquals(title2, TabAttributeCache.getTitle(TAB2_ID));
-        Assert.assertNotEquals(rootId2, TabAttributeCache.getRootId(TAB2_ID));
-
-        mTabModelSelectorObserverCaptor.getValue().onTabStateInitialized();
-
-        Assert.assertEquals(url1, TabAttributeCache.getUrl(TAB1_ID));
-        Assert.assertEquals(title1, TabAttributeCache.getTitle(TAB1_ID));
-        Assert.assertEquals(rootId1, TabAttributeCache.getRootId(TAB1_ID));
-        Assert.assertEquals(timestamp1, TabAttributeCache.getTimestampMillis(TAB1_ID));
-        Assert.assertEquals(searchTerm, TabAttributeCache.getLastSearchTerm(TAB1_ID));
-
-        Assert.assertEquals(url2, TabAttributeCache.getUrl(TAB2_ID));
-        Assert.assertEquals(title2, TabAttributeCache.getTitle(TAB2_ID));
-        Assert.assertEquals(rootId2, TabAttributeCache.getRootId(TAB2_ID));
-    }
-}
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index cf120cef..ba3266a 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -128,7 +128,6 @@
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
-import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupColorUtils;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
@@ -281,7 +280,6 @@
     @Mock GridLayoutManager.SpanSizeLookup mSpanSizeLookup;
     @Mock Profile mProfile;
     @Mock Tracker mTracker;
-    @Mock PseudoTab.TitleProvider mTitleProvider;
     @Mock UrlUtilities.Natives mUrlUtilitiesJniMock;
     @Mock OptimizationGuideBridgeFactory.Natives mOptimizationGuideBridgeFactoryJniMock;
     @Mock OptimizationGuideBridge mOptimizationGuideBridge;
@@ -463,7 +461,6 @@
 
     @After
     public void tearDown() {
-        PseudoTab.clearForTesting();
         ProfileManager.resetForTesting();
     }
 
@@ -483,16 +480,28 @@
     }
 
     @Test
-    public void updatesTitle_WithoutStoredTitle() {
+    public void updatesTitle_WithoutStoredTitle_Tab() {
         assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
 
-        when(mTitleProvider.getTitle(mActivity, PseudoTab.fromTab(mTab1))).thenReturn(NEW_TITLE);
+        when(mTab1.getTitle()).thenReturn(NEW_TITLE);
         mTabObserver.onTitleUpdated(mTab1);
 
         assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(NEW_TITLE));
     }
 
     @Test
+    public void updatesTitle_WithoutStoredTitle_TabGroup() {
+        Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
+        createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
+
+        mMediator.resetWithListOfTabs(tabs, false);
+
+        String defaultTitle = TabGroupTitleEditor.getDefaultTitle(mActivity, tabs.size());
+        assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(defaultTitle));
+    }
+
+    @Test
     public void updatesTitle_WithStoredTitle_TabGroup() {
         // Mock that tab1 and new tab are in the same group with root ID as TAB1_ID.
         Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
@@ -789,8 +798,7 @@
     public void sendsOpenGroupSignalCorrectly_SingleTabGroup() {
         List<Tab> tabs = Arrays.asList(mTab1);
         createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
-        mMediator.resetWithListOfTabs(
-                PseudoTab.getListOfPseudoTab(Arrays.asList(mTab1, mTab2)), false);
+        mMediator.resetWithListOfTabs(Arrays.asList(mTab1, mTab2), false);
         mModel.get(0)
                 .model
                 .get(TabProperties.TAB_SELECTED_LISTENER)
@@ -803,8 +811,7 @@
     public void sendsOpenGroupSignalCorrectly_TabGroup() {
         List<Tab> tabs = Arrays.asList(mTab1, mTab2);
         createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
-        mMediator.resetWithListOfTabs(
-                PseudoTab.getListOfPseudoTab(Arrays.asList(mTab1, mTab2)), false);
+        mMediator.resetWithListOfTabs(Arrays.asList(mTab1, mTab2), false);
         mModel.get(0)
                 .model
                 .get(TabProperties.TAB_SELECTED_LISTENER)
@@ -874,7 +881,7 @@
         RecyclerView.ViewHolder fakeViewHolder4 = prepareFakeViewHolder(itemView4, 3);
 
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3, tab4));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(4));
 
         // Merge 2 to 1.
@@ -1211,7 +1218,6 @@
                         mCurrentTabModelFilterSupplier,
                         () -> mTabModel,
                         getTabThumbnailCallback(),
-                        mTitleProvider,
                         mTabListFaviconProvider,
                         mTabGroupColorFaviconProvider,
                         true,
@@ -1300,7 +1306,7 @@
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
-        assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
+        assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo("2 tabs"));
         assertNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
     }
 
@@ -1343,7 +1349,7 @@
         createTabGroup(List.of(mTab1), TAB1_ID, TAB_GROUP_ID);
 
         setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(Arrays.asList(mTab1)), false);
+        mMediator.resetWithListOfTabs(Arrays.asList(mTab1), false);
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
@@ -1368,7 +1374,7 @@
         createTabGroup(List.of(mTab1), TAB1_ID, TAB_GROUP_ID);
 
         setUpTabListMediator(TabListMediatorType.TAB_GRID_DIALOG, TabListMode.GRID);
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(Arrays.asList(mTab1)), false);
+        mMediator.resetWithListOfTabs(Arrays.asList(mTab1), false);
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
@@ -1389,7 +1395,7 @@
     public void tabMoveOutOfGroup_GTS_Moved_Tab_Selected() {
         // Assume that two tabs are in the same group before ungroup.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab2));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
@@ -1418,7 +1424,7 @@
     public void tabMoveOutOfGroup_GTS_Origin_Tab_Selected() {
         // Assume that two tabs are in the same group before ungroup.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
@@ -1444,7 +1450,7 @@
     public void tabMoveOutOfGroup_GTS_LastTab() {
         // Assume that tab1 is a single tab group that became a single tab.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         doReturn(1).when(mTabGroupModelFilter).getCount();
         doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
         doReturn(tabs).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
@@ -1466,7 +1472,7 @@
     public void tabMoveOutOfGroup_GTS_TabAdditionWithSameId() {
         // Assume that two tabs are in the same group before ungroup.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
@@ -1590,7 +1596,7 @@
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
         doReturn(false).when(mTab1).isIncognito();
         doReturn(false).when(mTab2).isIncognito();
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
     }
 
     @Test
@@ -1620,7 +1626,7 @@
 
         // Assume that tab1 is a single tab.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         doReturn(1).when(mTabGroupModelFilter).getCount();
         doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
         doReturn(tabs).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
@@ -1752,7 +1758,7 @@
         doReturn(TAB3_ID).when(tab3).getRootId();
 
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(2));
 
         // Select tab3 so the group doesn't have the selected tab.
@@ -1817,7 +1823,7 @@
 
         // Select tab3 so the group doesn't have the selected tab.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(2));
 
         doReturn(2).when(mTabModel).index();
@@ -1881,7 +1887,7 @@
         doReturn(TAB1_ID).when(tab3).getRootId();
 
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(1));
 
         // Assume that moveTab in TabModel is finished.
@@ -1944,7 +1950,7 @@
         doReturn(TAB1_ID).when(tab3).getRootId();
 
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(1));
 
         // Assume that moveTab in TabModel is finished.
@@ -1987,7 +1993,7 @@
         // Assume there are 3 tabs in TabModel, mTab2 just grouped with mTab1;
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(2));
 
         // Assume undo grouping mTab2 with mTab1.
@@ -2009,7 +2015,7 @@
         // Assume there are 3 tabs in TabModel, tab3 just grouped with mTab1;
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(2));
 
         // Assume undo grouping tab3 with mTab1.
@@ -2035,7 +2041,7 @@
         // Assume there are 3 tabs in TabModel, mTab1 just grouped with mTab2;
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab2, tab3));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(2));
 
         // Assume undo grouping mTab1 from mTab2.
@@ -2063,7 +2069,7 @@
         Tab tab4 = prepareTab(TAB4_ID, TAB4_TITLE, TAB4_URL);
         doReturn(4).when(mTabModel).getCount();
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(1));
 
         // Assume undo grouping tab3 with mTab1.
@@ -2188,7 +2194,8 @@
         createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
 
         // Even if we have a stored title, we only show it in tab switcher.
-        assertThat(mMediator.getLatestTitleForTab(PseudoTab.fromTab(mTab1)), equalTo(TAB1_TITLE));
+        assertThat(
+                mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true), equalTo(TAB1_TITLE));
     }
 
     @Test
@@ -2205,7 +2212,7 @@
 
         // We never show stored title for single tab.
         assertThat(
-                mMediator.getLatestTitleForTab(PseudoTab.fromTab(mTab1)),
+                mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true),
                 equalTo(CUSTOMIZED_DIALOG_TITLE1));
     }
 
@@ -2223,7 +2230,8 @@
         when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
 
         // We never show stored title for single tab.
-        assertThat(mMediator.getLatestTitleForTab(PseudoTab.fromTab(mTab1)), equalTo(TAB1_TITLE));
+        assertThat(
+                mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true), equalTo(TAB1_TITLE));
     }
 
     @Test
@@ -2239,11 +2247,30 @@
         createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
 
         assertThat(
-                mMediator.getLatestTitleForTab(PseudoTab.fromTab(mTab1)),
+                mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true),
                 equalTo(CUSTOMIZED_DIALOG_TITLE1));
     }
 
     @Test
+    public void getLatestTitle_Default_GTS() {
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
+
+        assertThat(
+                mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ true), equalTo("2 tabs"));
+    }
+
+    @Test
+    public void getLatestTitle_NoDefault_GTS() {
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
+
+        assertThat(mMediator.getLatestTitleForTab(mTab1, /* useDefault= */ false), equalTo(""));
+    }
+
+    @Test
     public void updateTabGroupTitle_GTS() {
         setUpTabGroupCardDescriptionString();
         String targetString = "Expand tab group with 2 tabs.";
@@ -2322,7 +2349,7 @@
         assertEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
 
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), /* quickMode= */ false);
+        mMediator.resetWithListOfTabs(tabs, /* quickMode= */ false);
         assertThat(mModel.size(), equalTo(2));
         assertNotEquals(TabProperties.UiType.DIVIDER, mModel.get(0).type);
         assertNotEquals(TabProperties.UiType.DIVIDER, mModel.get(1).type);
@@ -2499,7 +2526,8 @@
         setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
 
         TabListMediator mMediatorSpy = spy(mMediator);
-        doReturn(true).when(mMediatorSpy).isPseudoTabInTabGroup(any());
+        when(mTabGroupModelFilter.isTabInTabGroup(any())).thenReturn(true);
+        doReturn(true).when(mMediatorSpy).isTabInTabGroup(any());
 
         mMediatorSpy.setComponentNameForTesting(TabSwitcherCoordinator.COMPONENT_NAME);
         initAndAssertAllProperties(mMediatorSpy);
@@ -2531,7 +2559,8 @@
         setUpTabListMediator(TabListMediatorType.TAB_SWITCHER, TabListMode.LIST);
 
         TabListMediator mMediatorSpy = spy(mMediator);
-        doReturn(true).when(mMediatorSpy).isPseudoTabInTabGroup(any());
+        when(mTabGroupModelFilter.isTabInTabGroup(any())).thenReturn(true);
+        doReturn(true).when(mMediatorSpy).isTabInTabGroup(any());
 
         mMediatorSpy.setComponentNameForTesting(TabSwitcherCoordinator.COMPONENT_NAME);
         initAndAssertAllProperties(mMediatorSpy);
@@ -2551,8 +2580,7 @@
         mModel.get(POSITION1).model.set(TabProperties.TAB_GROUP_COLOR_ID, COLOR_2);
 
         // Pass in a mocked newly created tab group.
-        mMediatorSpy.resetWithListOfTabs(
-                PseudoTab.getListOfPseudoTab(tabs), /* quickMode= */ false);
+        mMediatorSpy.resetWithListOfTabs(tabs, /* quickMode= */ false);
         assertEquals(
                 nextSuggestedColorId,
                 mModel.get(POSITION1).model.get(TabProperties.TAB_GROUP_COLOR_ID));
@@ -2690,9 +2718,7 @@
             tabs.add(mTabModel.getTabAt(i));
         }
 
-        boolean showQuickly =
-                mMediator.resetWithListOfTabs(
-                        PseudoTab.getListOfPseudoTab(tabs), /* quickMode= */ false);
+        boolean showQuickly = mMediator.resetWithListOfTabs(tabs, /* quickMode= */ false);
         assertThat(showQuickly, equalTo(true));
 
         // Create a PropertyModel that is not a tab and add it to the existing TabListModel.
@@ -2702,9 +2728,7 @@
         assertThat(mModel.size(), equalTo(tabs.size() + 1));
 
         // TabListModel unchange check should ignore the non-Tab item.
-        showQuickly =
-                mMediator.resetWithListOfTabs(
-                        PseudoTab.getListOfPseudoTab(tabs), /* quickMode= */ false);
+        showQuickly = mMediator.resetWithListOfTabs(tabs, /* quickMode= */ false);
         assertThat(showQuickly, equalTo(true));
     }
 
@@ -2716,7 +2740,7 @@
             for (boolean priceTrackingEnabled : new boolean[] {false, true}) {
                 for (boolean incognito : new boolean[] {false, true}) {
                     TabListMediator mMediatorSpy = spy(mMediator);
-                    doReturn(false).when(mMediatorSpy).isPseudoTabInTabGroup(any());
+                    doReturn(false).when(mMediatorSpy).isTabInTabGroup(any());
                     PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(
                             signedInAndSyncEnabled);
                     PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
@@ -2740,8 +2764,7 @@
                     tabs.add(mTabModel.getTabAt(0));
                     tabs.add(mTabModel.getTabAt(1));
 
-                    mMediatorSpy.resetWithListOfTabs(
-                            PseudoTab.getListOfPseudoTab(tabs), /* quickMode= */ false);
+                    mMediatorSpy.resetWithListOfTabs(tabs, /* quickMode= */ false);
                     if (signedInAndSyncEnabled && priceTrackingEnabled && !incognito) {
                         mModel.get(0)
                                 .model
@@ -2902,7 +2925,6 @@
                         mCurrentTabModelFilterSupplier,
                         () -> mTabModel,
                         getTabThumbnailCallback(),
-                        mTitleProvider,
                         mTabListFaviconProvider,
                         mTabGroupColorFaviconProvider,
                         true,
@@ -2937,7 +2959,6 @@
                         mCurrentTabModelFilterSupplier,
                         () -> mTabModel,
                         getTabThumbnailCallback(),
-                        mTitleProvider,
                         mTabListFaviconProvider,
                         mTabGroupColorFaviconProvider,
                         true,
@@ -3154,18 +3175,14 @@
         createTabGroup(group1, TAB2_ID, TAB_GROUP_ID);
 
         // Reset with show quickly.
-        assertThat(
-                mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false),
-                equalTo(true));
+        assertThat(mMediator.resetWithListOfTabs(tabs, false), equalTo(true));
         assertThat(
                 mModel.get(POSITION2).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
                 equalTo(targetString));
 
         // Reset without show quickly.
         mModel.clear();
-        assertThat(
-                mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false),
-                equalTo(false));
+        assertThat(mMediator.resetWithListOfTabs(tabs, false), equalTo(false));
         assertThat(
                 mModel.get(POSITION2).model.get(TabProperties.CONTENT_DESCRIPTION_STRING),
                 equalTo(targetString));
@@ -3193,7 +3210,7 @@
             tabs.add(mTabModel.getTabAt(i));
         }
 
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(
                 mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
                 equalTo(targetString));
@@ -3205,7 +3222,7 @@
         setUpCloseButtonDescriptionString(true);
         targetString = "Close tab group with 2 tabs.";
 
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(
                 mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
                 equalTo(targetString));
@@ -3231,7 +3248,7 @@
         setUpCloseButtonDescriptionString(true);
         String targetString = "Close tab group with 1 tab.";
 
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(
                 mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
                 equalTo(targetString));
@@ -3255,7 +3272,7 @@
         createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
         String targetString = "Close tab group with 2 tabs, color Grey.";
 
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(group1), false);
+        mMediator.resetWithListOfTabs(group1, false);
         assertThat(
                 mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
                 equalTo(targetString));
@@ -3282,7 +3299,7 @@
         createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
         String targetString = "Open the tab group action menu for tab group 2 tabs";
 
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(group1), false);
+        mMediator.resetWithListOfTabs(group1, false);
         assertThat(
                 mModel.get(POSITION1).model.get(TabProperties.ACTION_BUTTON_DESCRIPTION_STRING),
                 equalTo(targetString));
@@ -3342,7 +3359,7 @@
         tabs.add(mTabModel.getTabAt(0));
         tabs.add(mTabModel.getTabAt(1));
 
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), /* quickMode= */ false);
+        mMediator.resetWithListOfTabs(tabs, /* quickMode= */ false);
 
         prepareRecyclerViewForScroll();
         mMediator.registerOnScrolledListener(mRecyclerView);
@@ -3367,7 +3384,6 @@
                         mCurrentTabModelFilterSupplier,
                         () -> mTabModel,
                         getTabThumbnailCallback(),
-                        mTitleProvider,
                         mTabListFaviconProvider,
                         mTabGroupColorFaviconProvider,
                         true,
@@ -3386,7 +3402,7 @@
         when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(false);
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(3));
         assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
         assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
@@ -3413,7 +3429,6 @@
                         mCurrentTabModelFilterSupplier,
                         () -> mTabModel,
                         getTabThumbnailCallback(),
-                        mTitleProvider,
                         mTabListFaviconProvider,
                         mTabGroupColorFaviconProvider,
                         true,
@@ -3432,7 +3447,7 @@
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
         when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(false);
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(3));
         assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
         assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
@@ -3459,7 +3474,6 @@
                         mCurrentTabModelFilterSupplier,
                         () -> mTabModel,
                         getTabThumbnailCallback(),
-                        mTitleProvider,
                         mTabListFaviconProvider,
                         mTabGroupColorFaviconProvider,
                         true,
@@ -3487,7 +3501,7 @@
         when(mTabGroupModelFilter.isTabInTabGroup(tab3)).thenReturn(false);
         List<Tab> tabs = Arrays.asList(mTab1, mTab2, tab3);
         when(mSelectionDelegate.isItemSelected(TAB1_ID)).thenReturn(false);
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(3));
         assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(false));
         assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
@@ -3499,7 +3513,7 @@
         ThumbnailFetcher fetcher1 = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
         ThumbnailFetcher fetcher2 = mModel.get(1).model.get(TabProperties.THUMBNAIL_FETCHER);
         ThumbnailFetcher fetcher3 = mModel.get(2).model.get(TabProperties.THUMBNAIL_FETCHER);
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), true);
+        mMediator.resetWithListOfTabs(tabs, true);
 
         assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
         assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(true));
@@ -3543,7 +3557,7 @@
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
         createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
 
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(List.of(mTab1, mTab2)), true);
+        mMediator.resetWithListOfTabs(List.of(mTab1, mTab2), true);
         ThumbnailFetcher fetcherBefore = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
         assertEquals(2, mModel.size());
 
@@ -3568,7 +3582,7 @@
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, tab3));
         createTabGroup(tabs, TAB1_ID, TAB_GROUP_ID);
 
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(List.of(mTab1, mTab2)), true);
+        mMediator.resetWithListOfTabs(List.of(mTab1, mTab2), true);
         ThumbnailFetcher fetcherBefore = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
         assertEquals(2, mModel.size());
 
@@ -3627,7 +3641,7 @@
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
         List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, tab3));
         createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
 
         // Assert that a tab group status was recorded.
         assertTrue(mModel.get(POSITION1).model.get(TabProperties.TAB_GROUP_INFO).getIsTabGroup());
@@ -3643,7 +3657,7 @@
         // Create tab group.
         List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, mTab2));
         createTabGroup(group1, TAB1_ID, TAB_GROUP_ID);
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
 
         // Assert that the callback performs as expected.
         assertNotNull(mModel.get(POSITION1).model.get(TabProperties.ON_MENU_ITEM_CLICKED_CALLBACK));
@@ -3697,7 +3711,7 @@
         when(mTabListRecyclerView.getRectOfCurrentThumbnail(4, TAB7_ID)).thenReturn(null);
 
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3, tab5, tab7));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(5));
 
         TreeMap<Integer, List<Integer>> resultMap = new TreeMap<>();
@@ -3787,7 +3801,7 @@
         for (int i = 0; i < mTabModel.getCount(); i++) {
             tabs.add(mTabModel.getTabAt(i));
         }
-        mediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mediator.resetWithListOfTabs(tabs, false);
         for (Callback<TabFavicon> callback : mCallbackCaptor.getAllValues()) {
             callback.onResult(mFavicon);
         }
@@ -3797,8 +3811,12 @@
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
         assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
 
-        assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
-        assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
+        if (!mTabGroupModelFilter.isTabInTabGroup(mTab1)) {
+            assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
+        }
+        if (!mTabGroupModelFilter.isTabInTabGroup(mTab2)) {
+            assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
+        }
 
         assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON_FETCHER));
         assertNotNull(mModel.get(1).model.get(TabProperties.FAVICON_FETCHER));
@@ -3833,7 +3851,7 @@
         Tab tab = TabUiUnitTestUtils.prepareTab(id, title, url);
         when(tab.getView()).thenReturn(mock(View.class));
         doReturn(true).when(tab).isIncognito();
-        when(mTitleProvider.getTitle(mActivity, PseudoTab.fromTab(tab))).thenReturn(title);
+        when(tab.getTitle()).thenReturn(title);
         int count = mTabModel.getCount();
         doReturn(tab).when(mTabModel).getTabAt(count);
         doReturn(count).when(mTabModel).getCount();
@@ -3895,7 +3913,6 @@
                         mCurrentTabModelFilterSupplier,
                         () -> mTabModel,
                         getTabThumbnailCallback(),
-                        mTitleProvider,
                         mTabListFaviconProvider,
                         mTabGroupColorFaviconProvider,
                         actionOnRelatedTabs,
@@ -3984,7 +4001,7 @@
     private void initWithThreeTabs() {
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE, TAB3_URL);
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, tab3));
-        mMediator.resetWithListOfTabs(PseudoTab.getListOfPseudoTab(tabs), false);
+        mMediator.resetWithListOfTabs(tabs, false);
         assertThat(mModel.size(), equalTo(3));
         assertThat(mModel.get(0).model.get(TabProperties.IS_SELECTED), equalTo(true));
         assertThat(mModel.get(1).model.get(TabProperties.IS_SELECTED), equalTo(false));
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index bcf5d5c..f4d541e 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -5,8 +5,6 @@
 # These should reside within the public/ subdirectory, but cannot due to
 # dependency issues.
 public_tab_management_java_sources = [
-  "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTab.java",
-  "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCache.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ActionConfirmationDialog.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ActionConfirmationManager.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/CloseAllTabsDialog.java",
@@ -210,8 +208,6 @@
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneUnitTest.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabsSettingsUnitTest.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/WasPositiveControllerUnitTest.java",
-  "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/PseudoTabUnitTest.java",
-  "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/pseudotab/TabAttributeCacheUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtilsUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/CloseAllTabsDialogUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/ColorPickerItemViewBinderUnitTest.java",
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
index 8882e94f..01adb05 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceCoordinator.java
@@ -64,7 +64,6 @@
 import org.chromium.chrome.browser.xsurface.feed.FeedSurfaceScope;
 import org.chromium.chrome.browser.xsurface.feed.FeedUserInteractionReliabilityLogger;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
-import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 import org.chromium.components.browser_ui.widget.displaystyle.UiConfig;
 import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.feature_engagement.Tracker;
@@ -849,13 +848,8 @@
                                     return AppCompatResources.getDrawable(mActivity, resId);
                                 }));
             }
-            if (ChromeFeatureList.sSurfacePolish.isEnabled()) {
-                view.setBackground(
-                        AppCompatResources.getDrawable(
-                                mActivity, R.drawable.home_surface_background));
-            } else {
-                view.setBackgroundColor(SemanticColorUtils.getDefaultBgColor(mActivity));
-            }
+            view.setBackground(
+                    AppCompatResources.getDrawable(mActivity, R.drawable.home_surface_background));
 
             // Work around https://crbug.com/943873 where default focus highlight shows up after
             // toggling dark mode.
@@ -964,27 +958,23 @@
         for (View header : headerViews) {
             // Feed header view in multi does not need padding added.
             int lateralPaddingsPx = getLateralPaddingsPx();
-            if (header == mSectionHeaderView) {
-                lateralPaddingsPx = 0;
-            }
 
-            if (ChromeFeatureList.sSurfacePolish.isEnabled()) {
-                if (header instanceof NewTabPageLayout) {
-                    lateralPaddingsPx = 0;
-                } else if (header == mSectionHeaderView) {
-                    if (!ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_CONTAINMENT)) {
-                        mSectionHeaderView.setBackground(
-                                AppCompatResources.getDrawable(
-                                        mActivity, R.drawable.home_surface_background));
-                    }
-                } else if (header == mSigninPromoView) {
-                    lateralPaddingsPx =
-                            mActivity
-                                    .getResources()
-                                    .getDimensionPixelSize(R.dimen.signin_promo_lateral_paddings);
-                    ((PersonalizedSigninPromoView) mSigninPromoView)
-                            .setCardBackgroundResource(R.drawable.home_surface_ui_background);
+            if (header instanceof NewTabPageLayout) {
+                lateralPaddingsPx = 0;
+            } else if (header == mSectionHeaderView) {
+                lateralPaddingsPx = 0;
+                if (!ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_CONTAINMENT)) {
+                    mSectionHeaderView.setBackground(
+                            AppCompatResources.getDrawable(
+                                    mActivity, R.drawable.home_surface_background));
                 }
+            } else if (header == mSigninPromoView) {
+                lateralPaddingsPx =
+                        mActivity
+                                .getResources()
+                                .getDimensionPixelSize(R.dimen.signin_promo_lateral_paddings);
+                ((PersonalizedSigninPromoView) mSigninPromoView)
+                        .setCardBackgroundResource(R.drawable.home_surface_ui_background);
             }
 
             FeedListContentManager.NativeViewContent content =
diff --git a/chrome/android/java/res/layout/new_tab_page_layout.xml b/chrome/android/java/res/layout/new_tab_page_layout.xml
index 81532c9..329f11b 100644
--- a/chrome/android/java/res/layout/new_tab_page_layout.xml
+++ b/chrome/android/java/res/layout/new_tab_page_layout.xml
@@ -11,6 +11,7 @@
     android:id="@+id/ntp_content"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:background="@drawable/home_surface_background"
     android:layout_gravity="center_horizontal"
     android:orientation="vertical"
     android:gravity="center"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditor.java
index 98b0ae6..b7122a9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/settings/AutofillLocalIbanEditor.java
@@ -25,7 +25,6 @@
 import org.chromium.chrome.browser.autofill.PersonalDataManager;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.Iban;
 import org.chromium.chrome.browser.autofill.PersonalDataManagerFactory;
-import org.chromium.components.autofill.IbanRecordType;
 
 /**
  * This class creates a view for adding and editing a local IBAN. A local IBAN gets saved to the
@@ -84,14 +83,16 @@
         // case of a new IBAN, these values are set right before being written to the autofill
         // table.
         Iban iban =
-                Iban.createLocal(
-                        /* guid= */ mGUID,
-                        /* label= */ "",
-                        /* nickname= */ mNickname.getText().toString().trim(),
-                        /* recordType= */ mGUID.isEmpty()
-                                ? IbanRecordType.UNKNOWN
-                                : IbanRecordType.LOCAL_IBAN,
-                        /* value= */ mValue.getText().toString());
+                mGUID.isEmpty()
+                        ? Iban.createEphemeral(
+                                /* label= */ "",
+                                /* nickname= */ mNickname.getText().toString().trim(),
+                                /* value= */ mValue.getText().toString())
+                        : Iban.createLocal(
+                                /* guid= */ mGUID,
+                                /* label= */ "",
+                                /* nickname= */ mNickname.getText().toString().trim(),
+                                /* value= */ mValue.getText().toString());
         PersonalDataManager personalDataManager =
                 PersonalDataManagerFactory.getForProfile(getProfile());
         String guid = personalDataManager.addOrUpdateLocalIban(iban);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index dabcb2bf..4e9bd38 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -72,6 +72,7 @@
 import org.chromium.chrome.browser.theme.TopUiThemeColorProvider;
 import org.chromium.chrome.browser.ui.RootUiCoordinator;
 import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
+import org.chromium.chrome.browser.ui.google_bottom_bar.GoogleBottomBarCoordinator;
 import org.chromium.chrome.browser.usage_stats.UsageStatsService;
 import org.chromium.chrome.browser.webapps.SameTaskWebApkActivity;
 import org.chromium.chrome.browser.webapps.WebappActivityCoordinator;
@@ -657,6 +658,13 @@
 
     @Override
     public int getBaseStatusBarColor(Tab tab) {
+        // TODO(b/300419189): Pass the CCT Top Bar Color in AGSA intent after Google Bottom Bar is
+        // launched
+        if (GoogleBottomBarCoordinator.isFeatureEnabled()
+                && CustomTabsConnection.getInstance()
+                        .shouldEnableGoogleBottomBarForIntent(mIntentDataProvider)) {
+            return getWindow().getContext().getColor(R.color.google_bottom_bar_background_color);
+        }
         // TODO(b/300419189): Pass the CCT Top Bar Color in AGSA intent after the Chrome side LE for
         // Page Insights Hub
         if (PageInsightsCoordinator.isFeatureEnabled()
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 3e34b583..ad21de74 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
@@ -426,13 +426,9 @@
         mTitle = activity.getResources().getString(R.string.new_tab_title);
 
         mIsSurfacePolishEnabled = ChromeFeatureList.sSurfacePolish.isEnabled();
-        if (mIsSurfacePolishEnabled) {
-            mBackgroundColor =
-                    ChromeColors.getSurfaceColor(
-                            mContext, R.dimen.home_surface_background_color_elevation);
-        } else {
-            mBackgroundColor = SemanticColorUtils.getDefaultBgColor(mContext);
-        }
+        mBackgroundColor =
+                ChromeColors.getSurfaceColor(
+                        mContext, R.dimen.home_surface_background_color_elevation);
         mIsTablet = isTablet;
         mTemplateUrlService = TemplateUrlServiceFactory.getForProfile(profile);
         mTemplateUrlService.addObserver(this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
index f658472..227012f1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageLayout.java
@@ -317,11 +317,6 @@
         mInitialized = true;
 
         TraceEvent.end(TAG + ".initialize()");
-
-        if (mIsSurfacePolishEnabled) {
-            setBackground(
-                    AppCompatResources.getDrawable(mContext, R.drawable.home_surface_background));
-        }
     }
 
     public void reload() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java
index fb412d1..07a5476 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacySettings.java
@@ -394,7 +394,9 @@
 
     private boolean showTrackingProtectionUI() {
         return UserPrefs.get(getProfile()).getBoolean(Pref.TRACKING_PROTECTION3PCD_ENABLED)
-                || ChromeFeatureList.isEnabled(ChromeFeatureList.TRACKING_PROTECTION_3PCD);
+                || ChromeFeatureList.isEnabled(ChromeFeatureList.TRACKING_PROTECTION_3PCD)
+                || ChromeFeatureList.isEnabled(
+                        ChromeFeatureList.TRACKING_PROTECTION_SETTINGS_LAUNCH);
     }
 
     private boolean shouldShowIpProtectionUI() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/ChromeTrackingProtectionDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/ChromeTrackingProtectionDelegate.java
index 52432d6..0d006a79 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/ChromeTrackingProtectionDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/privacy_sandbox/ChromeTrackingProtectionDelegate.java
@@ -43,6 +43,12 @@
     }
 
     @Override
+    public boolean shouldDisplayIpProtection() {
+        return ChromeFeatureList.isEnabled(ChromeFeatureList.TRACKING_PROTECTION_SETTINGS_LAUNCH)
+                && ChromeFeatureList.isEnabled(ChromeFeatureList.IP_PROTECTION_V1);
+    }
+
+    @Override
     public boolean isIpProtectionEnabled() {
         return UserPrefs.get(mProfile).getBoolean(Pref.IP_PROTECTION_ENABLED)
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.IP_PROTECTION_V1);
@@ -55,6 +61,12 @@
     }
 
     @Override
+    public boolean shouldDisplayFingerprintingProtection() {
+        return ChromeFeatureList.isEnabled(ChromeFeatureList.TRACKING_PROTECTION_SETTINGS_LAUNCH)
+                && ChromeFeatureList.isEnabled(ChromeFeatureList.FINGERPRINTING_PROTECTION_SETTING);
+    }
+
+    @Override
     public boolean isFingerprintingProtectionEnabled() {
         return UserPrefs.get(mProfile).getBoolean(Pref.FINGERPRINTING_PROTECTION_ENABLED)
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.FINGERPRINTING_PROTECTION_SETTING);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicy.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicy.java
index e8e9951..840a7ea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabbedModeTabPersistencePolicy.java
@@ -49,6 +49,10 @@
     // This shared prefs file was used for storing tab group session counts. It is no longer in use.
     private static final String LEGACY_TAB_GROUP_PREFS_FILE = "tab_group_pref";
 
+    // This shared prefs file was used for storing tab properties to use before tabs had loaded for
+    // tab switching surfaces.
+    private static final String LEGACY_TAB_ATTRIBUTE_CACHE_FILE = "tab_attribute_cache";
+
     /** <M53 The name of the file where the old tab metadata file is saved per directory. */
     @VisibleForTesting static final String LEGACY_SAVED_STATE_FILE = "tab_state";
 
@@ -477,6 +481,8 @@
 
             ContextUtils.getApplicationContext()
                     .deleteSharedPreferences(LEGACY_TAB_GROUP_PREFS_FILE);
+            ContextUtils.getApplicationContext()
+                    .deleteSharedPreferences(LEGACY_TAB_ATTRIBUTE_CACHE_FILE);
 
             return null;
         }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java
index 5f0c2621..a142053 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java
@@ -40,7 +40,6 @@
 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.junit.MockitoJUnit;
@@ -48,7 +47,6 @@
 
 import org.chromium.base.BuildInfo;
 import org.chromium.base.test.util.ApplicationTestUtils;
-import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
@@ -90,8 +88,6 @@
     private static final String CHILD_EMAIL = "child.account@gmail.com";
     private static final String TEST_URL = "https://foo.com";
 
-    @Rule public final TestRule mCommandLineFlagRule = CommandLineFlags.getTestRule();
-
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     // TODO(crbug.com/40234741): Use IdentityIntegrationTestRule instead.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
index 211832f..672b9108 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
@@ -38,7 +38,6 @@
 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;
@@ -52,12 +51,10 @@
 import org.chromium.base.supplier.OneshotSupplier;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
-import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.DoNotBatch;
-import org.chromium.base.test.util.Features;
 import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.base.test.util.ScalableTimeout;
@@ -113,12 +110,8 @@
     private static final long ACTIVITY_WAIT_LONG_MS = TimeUnit.SECONDS.toMillis(10);
     private static final String TEST_ENROLLMENT_TOKEN = "enrollment-token";
 
-    @Rule public TestRule mProcessor = new Features.JUnitProcessor();
-
     @Rule public JniMocker mJniMocker = new JniMocker();
 
-    @Rule public TestRule mCommandLineFlagsRule = CommandLineFlags.getTestRule();
-
     @Rule
     public BasePartnerBrowserCustomizationIntegrationTestRule mCustomizationRule =
             new BasePartnerBrowserCustomizationIntegrationTestRule();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewHelperTest.java
index ab791fe9..3a4c64e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/paint_preview/StartupPaintPreviewHelperTest.java
@@ -16,9 +16,11 @@
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
+import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.base.ColdStartTracker;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.metrics.SimpleStartupForegroundSessionDetector;
 import org.chromium.chrome.browser.paint_preview.services.PaintPreviewTabServiceFactory;
@@ -91,11 +93,16 @@
      */
     @Test
     @MediumTest
+    @DisableFeatures({ChromeFeatureList.ANDROID_TAB_DECLUTTER})
     @Restriction(StartupPaintPreviewHelperTestRunner.RESTRICTION_TYPE_KEEP_ACTIVITIES)
-    @DisabledTest(message = "Very flaky. See crbug.com/333779543.")
+    @DisabledTest(message = "Pending revival. See crbug.com/333779543.")
     public void testDisplayOnStartup() throws ExecutionException {
         mActivityTestRule.startMainActivityWithURL(
                 mActivityTestRule.getTestServer().getURL(TEST_URL));
+        final ChromeTabbedActivity activity = mActivityTestRule.getActivity();
+        CriteriaHelper.pollUiThread(
+                () -> activity.getTabModelSelector().isTabStateInitialized(),
+                "Tab state never initialized.");
         final Tab tab = mActivityTestRule.getActivity().getActivityTab();
         CriteriaHelper.pollUiThread(
                 () ->
@@ -119,12 +126,15 @@
 
         // Emulate browser cold start. Paint preview should be shown on startup.
         pretendColdStartBeforeForegrounded();
-        ChromeTabbedActivity activity = mActivityTestRule.getActivity();
         TestThreadUtils.runOnUiThreadBlocking(activity::finish);
         CriteriaHelper.pollUiThread(activity::isDestroyed, "Activity didn't get destroyed.");
 
         mActivityTestRule.startMainActivityFromLauncher();
-        final Tab previewTab = mActivityTestRule.getActivity().getActivityTab();
+        final ChromeTabbedActivity newActivity = mActivityTestRule.getActivity();
+        CriteriaHelper.pollUiThread(
+                () -> newActivity.getTabModelSelector().isTabStateInitialized(),
+                "Tab state never initialized.");
+        final Tab previewTab = newActivity.getActivityTab();
         tabbedPaintPreview =
                 TestThreadUtils.runOnUiThreadBlocking(() -> TabbedPaintPreview.get(previewTab));
 
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index dadf62f..b5d4941 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -13249,7 +13249,7 @@
           {NUM_TABS, plural, =1 {<ph name="GROUP_TITLE">$1<ex>MyGroup</ex></ph> - 1 Tab} other {<ph name="GROUP_TITLE">$1<ex>MyGroup</ex></ph> - # Tabs}}
         </message>
         <message name="IDS_RECENTLY_CLOSED_GROUP_UNNAMED" desc="In Title Case: Title of recently closed group that does not have a name, in Recent Tabs menu. [ICU_Syntax]">
-          {NUM_TABS, plural, =1 {Unnamed Group - 1 Tab} other {Unnamed Group - # Tabs}}
+          {NUM_TABS, plural, =1 {1 Tab} other {# Tabs}}
         </message>
         <message name="IDS_RECENT_TABS_NO_DEVICE_TABS" desc="In Title Case: The label in the Recent Tabs menu in the wrench menu when there's no tabs from other devices.">
           No Tabs From Other Devices
@@ -13269,7 +13269,7 @@
           {NUM_TABS, plural, =1 {<ph name="GROUP_TITLE">$1<ex>MyGroup</ex></ph> - 1 tab} other {<ph name="GROUP_TITLE">$1<ex>MyGroup</ex></ph> - # tabs}}
         </message>
         <message name="IDS_RECENTLY_CLOSED_GROUP_UNNAMED" desc="Title of recently closed group that does not have a name, in Recent Tabs menu. [ICU_Syntax]">
-          {NUM_TABS, plural, =1 {Unnamed group - 1 tab} other {Unnamed group - # tabs}}
+          {NUM_TABS, plural, =1 {1 tab} other {# tabs}}
         </message>
         <message name="IDS_RECENT_TABS_NO_DEVICE_TABS" desc="The label in the Recent Tabs menu in the wrench menu when there's no tabs from other devices.">
           No tabs from other devices
diff --git a/chrome/app/generated_resources_grd/IDS_RECENTLY_CLOSED_GROUP_UNNAMED.png.sha1 b/chrome/app/generated_resources_grd/IDS_RECENTLY_CLOSED_GROUP_UNNAMED.png.sha1
index a96cca59..400bd7a8 100644
--- a/chrome/app/generated_resources_grd/IDS_RECENTLY_CLOSED_GROUP_UNNAMED.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_RECENTLY_CLOSED_GROUP_UNNAMED.png.sha1
@@ -1 +1 @@
-d88b233d7a8c0e8acb3f39bd2c7a66e73204ee93
\ No newline at end of file
+7b11c1f844b6de239215cf3bf35887e52b480116
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 0c363bea..b1c0ac6 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -307,6 +307,7 @@
 #endif
 
 #if BUILDFLAG(IS_MAC)
+#include "chrome/browser/enterprise/platform_auth/platform_auth_features.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #endif  // BUILDFLAG(IS_MAC)
 
@@ -3850,6 +3851,14 @@
      std::size(kLocationProviderManagerModeHybridPlatform), nullptr}};
 #endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
 
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
+const FeatureEntry::FeatureParam kWebAuthnEnclaveAuthenticatorEnabledParam = {
+    device::kWebAuthnGpmPinFeatureParameterName, "true"};
+const FeatureEntry::FeatureVariation kWebAuthnEnclaveAuthenticatorVariations[] =
+    {{"with GPM PIN enabled", &kWebAuthnEnclaveAuthenticatorEnabledParam, 1,
+      nullptr}};
+#endif
+
 // RECORDING USER METRICS FOR FLAGS:
 // -----------------------------------------------------------------------------
 // The first line of the entry is the internal name.
@@ -5610,9 +5619,6 @@
     {"enable-federated-service", flag_descriptions::kFederatedServiceName,
      flag_descriptions::kFederatedServiceDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kFederatedService)},
-    {"screencast-v2", flag_descriptions::kScreencastV2Name,
-     flag_descriptions::kScreencastV2Description, kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kProjectorV2)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
     {"enable-cros-touch-text-editing-redesign",
      flag_descriptions::kTouchTextEditingRedesignName,
@@ -5626,12 +5632,14 @@
      FEATURE_VALUE_TYPE(features::kQuickOfficeForceFileDownload)},
 #endif  // BUILDFLAG(IS_CHROMEOS)
 #if BUILDFLAG(IS_MAC)
+    {"enable-extensible-enterprise-sso",
+     flag_descriptions::kEnableExtensibleEnterpriseSSOName,
+     flag_descriptions::kEnableExtensibleEnterpriseSSODescription, kOsMac,
+     FEATURE_VALUE_TYPE(enterprise_auth::kEnableExtensibleEnterpriseSSO)},
     {"enable-retry-capture-device-enumeration-on-crash",
      flag_descriptions::kRetryGetVideoCaptureDeviceInfosName,
      flag_descriptions::kRetryGetVideoCaptureDeviceInfosDescription, kOsMac,
      FEATURE_VALUE_TYPE(features::kRetryGetVideoCaptureDeviceInfos)},
-#endif  // BUILDFLAG(IS_MAC)
-#if BUILDFLAG(IS_MAC)
     {"enable-immersive-fullscreen-toolbar",
      flag_descriptions::kImmersiveFullscreenName,
      flag_descriptions::kImmersiveFullscreenDescription, kOsMac,
@@ -11363,6 +11371,16 @@
      SINGLE_VALUE_TYPE(
          optimization_guide::switches::kEnableModelQualityDogfoodLogging)},
 
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
+    {"web-authentication-enclave-authenticator",
+     flag_descriptions::kWebAuthnEnclaveAuthenticatorName,
+     flag_descriptions::kWebAuthnEnclaveAuthenticatorDescription,
+     kOsMac | kOsWin | kOsLinux,
+     FEATURE_WITH_PARAMS_VALUE_TYPE(device::kWebAuthnEnclaveAuthenticator,
+                                    kWebAuthnEnclaveAuthenticatorVariations,
+                                    "WebAuthenticationEnclaveAuthenticator")},
+#endif
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/android/browsing_data/browsing_data_bridge.cc b/chrome/browser/android/browsing_data/browsing_data_bridge.cc
index 8bac2bc..2e2171a 100644
--- a/chrome/browser/android/browsing_data/browsing_data_bridge.cc
+++ b/chrome/browser/android/browsing_data/browsing_data_bridge.cc
@@ -26,7 +26,7 @@
 #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
 #include "chrome/browser/engagement/important_sites_util.h"
 #include "chrome/browser/history/web_history_service_factory.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/common/channel_info.h"
 #include "components/browsing_data/content/android/browsing_data_model_android.h"
@@ -60,7 +60,7 @@
 }
 
 PrefService* GetPrefService(const JavaParamRef<jobject>& jprofile) {
-  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
+  Profile* profile = Profile::FromJavaObject(jprofile);
   return profile->GetOriginalProfile()->GetPrefs();
 }
 
@@ -97,7 +97,7 @@
     const JavaParamRef<jintArray>& jignoring_domain_reasons) {
   TRACE_EVENT0("browsing_data", "BrowsingDataBridge_ClearBrowsingData");
 
-  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
+  Profile* profile = Profile::FromJavaObject(jprofile);
   BrowsingDataRemover* browsing_data_remover =
       profile->GetBrowsingDataRemover();
 
@@ -191,7 +191,7 @@
       "browsing_data",
       "BrowsingDataBridge_RequestInfoAboutOtherFormsOfBrowsingHistory");
   // The one-time notice in the dialog.
-  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
+  Profile* profile = Profile::FromJavaObject(jprofile);
   browsing_data::ShouldPopupDialogAboutOtherFormsOfBrowsingHistory(
       SyncServiceFactory::GetForProfile(profile),
       WebHistoryServiceFactory::GetForProfile(profile), chrome::GetChannel(),
@@ -204,7 +204,7 @@
     const JavaParamRef<jobject>& jprofile,
     const JavaParamRef<jobject>& java_callback) {
   TRACE_EVENT0("browsing_data", "BrowsingDataBridge_FetchImportantSites");
-  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
+  Profile* profile = Profile::FromJavaObject(jprofile);
   std::vector<site_engagement::ImportantSitesUtil::ImportantDomainInfo>
       important_sites =
           site_engagement::ImportantSitesUtil::GetImportantRegisterableDomains(
@@ -246,7 +246,7 @@
   GURL origin(base::android::ConvertJavaStringToUTF8(jorigin));
   CHECK(origin.is_valid());
   site_engagement::ImportantSitesUtil::MarkOriginAsImportantForTesting(
-      ProfileAndroid::FromProfileAndroid(jprofile), origin);
+      Profile::FromJavaObject(jprofile), origin);
 }
 
 static jboolean JNI_BrowsingDataBridge_GetBrowsingDataDeletionPreference(
@@ -347,7 +347,7 @@
     JNIEnv* env,
     const JavaParamRef<jobject>& jprofile,
     const JavaParamRef<jobject>& java_callback) {
-  Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
+  Profile* profile = Profile::FromJavaObject(jprofile);
   BrowsingDataModel::BuildFromDisk(
       profile, ChromeBrowsingDataModelDelegate::CreateForProfile(profile),
       base::BindOnce(&OnBrowsingDataModelBuilt, env,
diff --git a/chrome/browser/android/content/web_contents_factory.cc b/chrome/browser/android/content/web_contents_factory.cc
index de4ddad..a4461ca 100644
--- a/chrome/browser/android/content/web_contents_factory.cc
+++ b/chrome/browser/android/content/web_contents_factory.cc
@@ -6,7 +6,6 @@
 #include "chrome/browser/android/content/web_contents_factory_data_deleter.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/browser/storage_partition_config.h"
 #include "content/public/browser/web_contents.h"
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
index b36d1d1e..c1e546634 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -7,6 +7,7 @@
 #include <jni.h>
 #include <stddef.h>
 #include <stdint.h>
+
 #include <memory>
 #include <string>
 #include <vector>
@@ -37,7 +38,6 @@
 #include "chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_factory.h"
 #include "chrome/browser/preloading/prerender/prerender_manager.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/common/webui_url_constants.h"
 #include "components/browser_ui/util/android/url_constants.h"
@@ -133,7 +133,7 @@
     : profile_{profile},
       java_controller_{Java_AutocompleteController_Constructor(
           AttachCurrentThread(),
-          ProfileAndroid::FromProfile(profile)->GetJavaObject(),
+          profile->GetJavaObject(),
           reinterpret_cast<intptr_t>(this))},
       autocomplete_controller_{std::make_unique<AutocompleteController>(
           std::move(client),
diff --git a/chrome/browser/android/omnibox/geolocation_header.cc b/chrome/browser/android/omnibox/geolocation_header.cc
index 82bcb2c..b8636f9 100644
--- a/chrome/browser/android/omnibox/geolocation_header.cc
+++ b/chrome/browser/android/omnibox/geolocation_header.cc
@@ -7,7 +7,6 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/ui/android/omnibox/jni_headers/GeolocationHeader_jni.h"
 #include "url/android/gurl_android.h"
 #include "url/gurl.h"
@@ -20,12 +19,9 @@
 std::optional<std::string> GetGeolocationHeaderIfAllowed(const GURL& url,
                                                          Profile* profile) {
   JNIEnv* env = base::android::AttachCurrentThread();
-  ProfileAndroid* profile_android = ProfileAndroid::FromProfile(profile);
-  DCHECK(profile_android);
 
   base::android::ScopedJavaLocalRef<jobject> j_profile_android =
-      profile_android->GetJavaObject();
-  DCHECK(!j_profile_android.is_null());
+      profile->GetJavaObject();
 
   base::android::ScopedJavaLocalRef<jstring> geo_header =
       Java_GeolocationHeader_getGeoHeader(
diff --git a/chrome/browser/android/signin/signin_manager_android.cc b/chrome/browser/android/signin/signin_manager_android.cc
index 68dd0164..e564fb6 100644
--- a/chrome/browser/android/signin/signin_manager_android.cc
+++ b/chrome/browser/android/signin/signin_manager_android.cc
@@ -23,7 +23,6 @@
 #include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h"
 #include "chrome/browser/policy/cloud/user_policy_signin_service_mobile.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/signin/account_id_from_account_info.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/sync_service_factory.h"
@@ -171,7 +170,7 @@
 
   java_signin_manager_ = Java_SigninManagerImpl_create(
       base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
-      ProfileAndroid::FromProfile(profile_)->GetJavaObject(),
+      profile_->GetJavaObject(),
       identity_manager_->LegacyGetAccountTrackerServiceJavaObject(),
       identity_manager_->GetJavaObject(),
       identity_manager_->GetIdentityMutatorJavaObject(),
diff --git a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
index 45584701..2cb09e1 100644
--- a/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_data_migrator_unittest.cc
@@ -12,13 +12,13 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/memory/raw_ptr.h"
-#include "base/run_loop.h"
 #include "base/strings/string_piece.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_command_line.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "base/test/test_future.h"
 #include "base/version.h"
 #include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
@@ -83,17 +83,12 @@
   ash::standalone_browser::migrator_util::UpdateMigrationAttemptCountForUser(
       &pref_service_, user_id_hash);
 
-  base::RunLoop run_loop;
+  base::test::TestFuture<BrowserDataMigrator::Result> future;
   std::unique_ptr<BrowserDataMigratorImpl> migrator =
       std::make_unique<BrowserDataMigratorImpl>(
           from_dir_, user_id_hash, base::DoNothing(), &pref_service_);
-  std::optional<BrowserDataMigrator::Result> result;
-  migrator->Migrate(base::BindLambdaForTesting(
-      [&out_result = result, &run_loop](BrowserDataMigrator::Result result) {
-        run_loop.Quit();
-        out_result = result;
-      }));
-  run_loop.Run();
+  migrator->Migrate(future.GetCallback());
+  BrowserDataMigrator::Result result = future.Take();
 
   const base::FilePath new_user_data_dir =
       from_dir_.Append(browser_data_migrator_util::kLacrosDir);
@@ -105,8 +100,7 @@
   EXPECT_TRUE(
       ash::standalone_browser::migrator_util::
           IsProfileMigrationCompletedForUser(&pref_service_, user_id_hash));
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(BrowserDataMigrator::ResultKind::kSucceeded, result->kind);
+  EXPECT_EQ(BrowserDataMigrator::ResultKind::kSucceeded, result.kind);
   EXPECT_EQ(BrowserDataMigratorImpl::GetMigrationStep(&pref_service_),
             BrowserDataMigratorImpl::MigrationStep::kEnded);
   // Successful migration should clear the migration attempt count.
@@ -131,18 +125,13 @@
   ash::standalone_browser::migrator_util::UpdateMigrationAttemptCountForUser(
       &pref_service_, user_id_hash);
 
-  base::RunLoop run_loop;
+  base::test::TestFuture<BrowserDataMigrator::Result> future;
   std::unique_ptr<BrowserDataMigratorImpl> migrator =
       std::make_unique<BrowserDataMigratorImpl>(
           from_dir_, user_id_hash, base::DoNothing(), &pref_service_);
-  std::optional<BrowserDataMigrator::Result> result;
-  migrator->Migrate(base::BindLambdaForTesting(
-      [&out_result = result, &run_loop](BrowserDataMigrator::Result result) {
-        run_loop.Quit();
-        out_result = result;
-      }));
+  migrator->Migrate(future.GetCallback());
   migrator->Cancel();
-  run_loop.Run();
+  BrowserDataMigrator::Result result = future.Take();
 
   const base::FilePath new_user_data_dir =
       from_dir_.Append(browser_data_migrator_util::kLacrosDir);
@@ -152,8 +141,7 @@
   EXPECT_FALSE(
       ash::standalone_browser::migrator_util::
           IsProfileMigrationCompletedForUser(&pref_service_, user_id_hash));
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(BrowserDataMigrator::ResultKind::kCancelled, result->kind);
+  EXPECT_EQ(BrowserDataMigrator::ResultKind::kCancelled, result.kind);
   EXPECT_EQ(BrowserDataMigratorImpl::GetMigrationStep(&pref_service_),
             BrowserDataMigratorImpl::MigrationStep::kEnded);
   // If migration fails, migration attempt count should not be cleared thus
@@ -179,22 +167,16 @@
   BrowserDataMigratorImpl::SetMigrationStep(
       &pref_service_, BrowserDataMigratorImpl::MigrationStep::kRestartCalled);
 
-  base::RunLoop run_loop;
+  base::test::TestFuture<BrowserDataMigrator::Result> future;
   std::unique_ptr<BrowserDataMigratorImpl> migrator =
       std::make_unique<BrowserDataMigratorImpl>(
           from_dir_, user_id_hash, base::DoNothing(), &pref_service_);
-  std::optional<BrowserDataMigrator::Result> result;
-  migrator->Migrate(base::BindLambdaForTesting(
-      [&out_result = result, &run_loop](BrowserDataMigrator::Result result) {
-        run_loop.Quit();
-        out_result = result;
-      }));
-  run_loop.Run();
+  migrator->Migrate(future.GetCallback());
+  BrowserDataMigrator::Result result = future.Take();
 
-  ASSERT_TRUE(result.has_value());
-  EXPECT_EQ(BrowserDataMigrator::ResultKind::kFailed, result->kind);
+  EXPECT_EQ(BrowserDataMigrator::ResultKind::kFailed, result.kind);
   // |required_size| should carry the data.
-  EXPECT_EQ(100u, result->required_size);
+  EXPECT_EQ(100u, result.required_size);
 }
 
 class BrowserDataMigratorRestartTest : public ::testing::Test {
@@ -297,16 +279,12 @@
     base::test::ScopedCommandLine command_line;
     command_line.GetProcessCommandLine()->AppendSwitchASCII(
         switches::kForceBrowserDataMigrationForTesting, "force-skip");
-    std::optional<bool> result;
+    base::test::TestFuture<bool, const std::optional<uint64_t>&> future;
     BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
-        user->GetAccountId(), user->username_hash(),
-        base::BindLambdaForTesting(
-            [&out_result = result](bool result,
-                                   const std::optional<uint64_t>& size) {
-              out_result = result;
-            }));
-    EXPECT_TRUE(result.has_value());
-    EXPECT_FALSE(result.value());
+        user->GetAccountId(), user->username_hash(), future.GetCallback());
+
+    bool success = future.Get<0>();
+    EXPECT_FALSE(success);
     EXPECT_FALSE(restart_called);
   }
 
@@ -321,21 +299,15 @@
     browser_data_migrator_util::ScopedExtraBytesRequiredToBeFreedForTesting
         scoped_extra_bytes(1024 * 1024);
 
-    std::optional<bool> result;
-    std::optional<uint64_t> out_size;
-    base::RunLoop run_loop;
+    base::test::TestFuture<bool, const std::optional<uint64_t>> future;
     BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
         user->GetAccountId(), user->username_hash(),
-        base::BindLambdaForTesting(
-            [&out_result = result, &out_size, &run_loop](
-                bool result, const std::optional<uint64_t>& size) {
-              run_loop.Quit();
-              out_result = result;
-              out_size = size;
-            }));
-    run_loop.Run();
-    ASSERT_TRUE(result.has_value());
-    EXPECT_FALSE(result.value());
+        future.GetCallback<bool, const std::optional<uint64_t>&>());
+
+    bool success = future.Get<0>();
+    std::optional<uint64_t> out_size = future.Get<1>();
+
+    EXPECT_FALSE(success);
     EXPECT_EQ(1024u * 1024u, out_size);
     EXPECT_FALSE(restart_called);
   }
@@ -349,19 +321,14 @@
     browser_data_migrator_util::ScopedExtraBytesRequiredToBeFreedForTesting
         scoped_extra_bytes(0);
 
-    std::optional<bool> result;
-    base::RunLoop run_loop;
+    base::test::TestFuture<bool, const std::optional<uint64_t>> future;
     BrowserDataMigratorImpl::MaybeRestartToMigrateWithDiskCheck(
         user->GetAccountId(), user->username_hash(),
-        base::BindLambdaForTesting(
-            [&out_result = result, &run_loop](
-                bool result, const std::optional<uint64_t>& size) {
-              run_loop.Quit();
-              out_result = result;
-            }));
-    run_loop.Run();
-    ASSERT_TRUE(result.has_value());
-    EXPECT_TRUE(result.value());
+        future.GetCallback<bool, const std::optional<uint64_t>&>());
+
+    bool success = future.Get<0>();
+
+    EXPECT_TRUE(success);
     EXPECT_TRUE(restart_called);
   }
 }
diff --git a/chrome/browser/ash/crosapi/crosapi_util.cc b/chrome/browser/ash/crosapi/crosapi_util.cc
index 5660927..0e95754 100644
--- a/chrome/browser/ash/crosapi/crosapi_util.cc
+++ b/chrome/browser/ash/crosapi/crosapi_util.cc
@@ -956,6 +956,8 @@
 
   params->is_cros_mall_enabled = chromeos::features::IsCrosMallEnabled();
 
+  params->is_magic_boost_enabled = chromeos::features::IsMagicBoostEnabled();
+
   params->is_mahi_enabled = chromeos::features::IsMahiEnabled();
 
   params->is_container_app_preinstall_enabled =
diff --git a/chrome/browser/ash/crosapi/screen_manager_ash_browsertest.cc b/chrome/browser/ash/crosapi/screen_manager_ash_browsertest.cc
index 5b9f09e..7cda949 100644
--- a/chrome/browser/ash/crosapi/screen_manager_ash_browsertest.cc
+++ b/chrome/browser/ash/crosapi/screen_manager_ash_browsertest.cc
@@ -2,14 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/ash/crosapi/screen_manager_ash.h"
+
 #include <memory>
 
 #include "ash/public/cpp/test/shell_test_api.h"
+#include "base/functional/callback_forward.h"
+#include "base/location.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
-#include "chrome/browser/ash/crosapi/screen_manager_ash.h"
+#include "base/test/test_future.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/test/base/in_process_browser_test.h"
@@ -70,6 +74,13 @@
     background_sequence_->PostTask(FROM_HERE, std::move(bind_background));
   }
 
+  void PostTaskAndWait(base::OnceClosure closure) {
+    base::test::TestFuture<void> future;
+    background_sequence_->PostTaskAndReply(FROM_HERE, std::move(closure),
+                                           future.GetCallback());
+    EXPECT_TRUE(future.Wait());
+  }
+
   // Affine to main sequence.
   std::unique_ptr<ScreenManagerAsh> screen_manager_;
 
@@ -81,7 +92,6 @@
 };
 
 IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, ScreenCapturer) {
-  base::RunLoop run_loop;
   bool success;
   SkBitmap snapshot;
 
@@ -105,9 +115,7 @@
         }
       },
       screen_manager_remote_.get(), &success, &snapshot);
-  background_sequence_->PostTaskAndReply(
-      FROM_HERE, std::move(take_snapshot_background), run_loop.QuitClosure());
-  run_loop.Run();
+  PostTaskAndWait(std::move(take_snapshot_background));
 
   // Check that the IPC succeeded.
   ASSERT_TRUE(success);
@@ -121,7 +129,6 @@
 
 IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest,
                        ScreenCapturer_MultipleDisplays) {
-  base::RunLoop run_loop;
   bool success[2];
   SkBitmap snapshot[2];
 
@@ -149,9 +156,7 @@
         }
       },
       screen_manager_remote_.get(), success, snapshot);
-  background_sequence_->PostTaskAndReply(
-      FROM_HERE, std::move(take_snapshot_background), run_loop.QuitClosure());
-  run_loop.Run();
+  PostTaskAndWait(std::move(take_snapshot_background));
 
   // Check that the IPCs succeeded.
   ASSERT_TRUE(success[0]);
@@ -165,7 +170,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, WindowCapturer) {
-  base::RunLoop run_loop;
   bool success;
   SkBitmap snapshot;
 
@@ -189,9 +193,7 @@
         }
       },
       screen_manager_remote_.get(), &success, &snapshot);
-  background_sequence_->PostTaskAndReply(
-      FROM_HERE, std::move(take_snapshot_background), run_loop.QuitClosure());
-  run_loop.Run();
+  PostTaskAndWait(std::move(take_snapshot_background));
 
   // Check that the IPC succeeded.
   ASSERT_TRUE(success);
diff --git a/chrome/browser/ash/crosapi/search_controller_ash_unittest.cc b/chrome/browser/ash/crosapi/search_controller_ash_unittest.cc
index c666689..2619f83 100644
--- a/chrome/browser/ash/crosapi/search_controller_ash_unittest.cc
+++ b/chrome/browser/ash/crosapi/search_controller_ash_unittest.cc
@@ -47,10 +47,10 @@
   }
 
   void RunUntilSearch() {
-    base::RunLoop loop;
-    base::AutoReset<base::RepeatingClosure> quit_loop(&search_callback_,
-                                                      loop.QuitClosure());
-    loop.Run();
+    base::test::TestFuture<void> future;
+    base::AutoReset<base::RepeatingClosure> quit_loop(
+        &search_callback_, future.GetRepeatingCallback());
+    EXPECT_TRUE(future.Wait());
   }
 
   void ProduceResults(
@@ -96,11 +96,9 @@
         std::make_unique<SearchControllerAsh>(mojom_controller.BindToRemote());
   }
   {
-    base::RunLoop loop;
-    controller->AddDisconnectHandler(
-        SearchControllerAsh::DisconnectCallback(base::DoNothing())
-            .Then(loop.QuitClosure()));
-    loop.Run();
+    DisconnectTestFuture future1;
+    controller->AddDisconnectHandler(future1.GetCallback());
+    EXPECT_TRUE(future1.Wait());
   }
   controller->Search(u"cat", future.GetRepeatingCallback());
 
@@ -345,11 +343,9 @@
   SearchControllerAsh controller(mojom_controller->BindToRemote());
   mojom_controller.reset();
   {
-    base::RunLoop loop;
-    controller.AddDisconnectHandler(
-        SearchControllerAsh::DisconnectCallback(base::DoNothing())
-            .Then(loop.QuitClosure()));
-    loop.Run();
+    DisconnectTestFuture future1;
+    controller.AddDisconnectHandler(future1.GetCallback());
+    EXPECT_TRUE(future1.Wait());
   }
   ASSERT_FALSE(controller.IsConnected());
 
diff --git a/chrome/browser/ash/crosapi/video_conference_ash_browsertest.cc b/chrome/browser/ash/crosapi/video_conference_ash_browsertest.cc
index 3c50aa0..991856b0 100644
--- a/chrome/browser/ash/crosapi/video_conference_ash_browsertest.cc
+++ b/chrome/browser/ash/crosapi/video_conference_ash_browsertest.cc
@@ -2,20 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ash/video_conference/video_conference_manager_ash.h"
-
 #include <utility>
 #include <vector>
 
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_switches.h"
 #include "base/command_line.h"
-#include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "base/unguessable_token.h"
 #include "chrome/browser/ash/crosapi/crosapi_ash.h"
 #include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "chrome/browser/ash/video_conference/video_conference_manager_ash.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chromeos/crosapi/mojom/video_conference.mojom.h"
 #include "content/public/test/browser_test.h"
@@ -82,47 +81,35 @@
 
 // Calls all crosapi::mojom::VideoConference methods over mojo.
 void CallVcManagerAshMethods(FakeVcManagerMojoClient& client) {
-  base::RunLoop run_loop1;
+  base::test::TestFuture<bool> future1;
   client.remote_->NotifyMediaUsageUpdate(
       crosapi::mojom::VideoConferenceMediaUsageStatus::New(
           client.id_, true, false, true, false, true, false),
-      base::BindLambdaForTesting([&](bool success) {
-        EXPECT_TRUE(success);
-        run_loop1.Quit();
-      }));
-  run_loop1.Run();
+      future1.GetCallback());
+  EXPECT_TRUE(future1.Take());
 
-  base::RunLoop run_loop2;
+  base::test::TestFuture<bool> future2;
   client.remote_->NotifyDeviceUsedWhileDisabled(
       crosapi::mojom::VideoConferenceMediaDevice::kCamera, u"Test App",
-      base::BindLambdaForTesting([&](bool success) {
-        EXPECT_TRUE(success);
-        run_loop2.Quit();
-      }));
-  run_loop2.Run();
+      future2.GetCallback());
+  EXPECT_TRUE(future2.Take());
 }
 
 // Calls all crosapi::mojom::VideoConference methods directly.
 void CallVcManagerAshMethods(FakeVcManagerCppClient& client,
                              ash::VideoConferenceManagerAsh* vc_manager) {
-  base::RunLoop run_loop1;
+  base::test::TestFuture<bool> future1;
   vc_manager->NotifyMediaUsageUpdate(
       crosapi::mojom::VideoConferenceMediaUsageStatus::New(
           client.id_, true, true, false, false, true, true),
-      base::BindLambdaForTesting([&](bool success) {
-        EXPECT_TRUE(success);
-        run_loop1.Quit();
-      }));
-  run_loop1.Run();
+      future1.GetCallback());
+  EXPECT_TRUE(future1.Take());
 
-  base::RunLoop run_loop2;
+  base::test::TestFuture<bool> future2;
   vc_manager->NotifyDeviceUsedWhileDisabled(
       crosapi::mojom::VideoConferenceMediaDevice::kCamera, u"Test App",
-      base::BindLambdaForTesting([&](bool success) {
-        EXPECT_TRUE(success);
-        run_loop2.Quit();
-      }));
-  run_loop2.Run();
+      future2.GetCallback());
+  EXPECT_TRUE(future2.Take());
 }
 
 class VideoConferenceAshBrowserTest : public InProcessBrowserTest {
@@ -157,14 +144,11 @@
     FakeVcManagerMojoClient mojo_client1;
     vc_manager->BindReceiver(mojo_client1.remote_.BindNewPipeAndPassReceiver());
 
-    base::RunLoop run_loop1;
+    base::test::TestFuture<bool> future1;
     mojo_client1.remote_->RegisterMojoClient(
         mojo_client1.receiver_.BindNewPipeAndPassRemote(), mojo_client1.id_,
-        base::BindLambdaForTesting([&](bool success) {
-          EXPECT_TRUE(success);
-          run_loop1.Quit();
-        }));
-    run_loop1.Run();
+        future1.GetCallback());
+    EXPECT_TRUE(future1.Take());
 
     FakeVcManagerCppClient cpp_client1;
     vc_manager->RegisterCppClient(&cpp_client1, cpp_client1.id_);
@@ -178,14 +162,11 @@
   FakeVcManagerMojoClient mojo_client2;
   vc_manager->BindReceiver(mojo_client2.remote_.BindNewPipeAndPassReceiver());
 
-  base::RunLoop run_loop1;
+  base::test::TestFuture<bool> future2;
   mojo_client2.remote_->RegisterMojoClient(
       mojo_client2.receiver_.BindNewPipeAndPassRemote(), mojo_client2.id_,
-      base::BindLambdaForTesting([&](bool success) {
-        EXPECT_TRUE(success);
-        run_loop1.Quit();
-      }));
-  run_loop1.Run();
+      future2.GetCallback());
+  EXPECT_TRUE(future2.Take());
 
   FakeVcManagerCppClient cpp_client2;
   vc_manager->RegisterCppClient(&cpp_client2, cpp_client2.id_);
diff --git a/chrome/browser/ash/crosapi/wallpaper_ash_unittest.cc b/chrome/browser/ash/crosapi/wallpaper_ash_unittest.cc
index 4d8dab0b..c6e804b 100644
--- a/chrome/browser/ash/crosapi/wallpaper_ash_unittest.cc
+++ b/chrome/browser/ash/crosapi/wallpaper_ash_unittest.cc
@@ -9,6 +9,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/bind.h"
+#include "base/test/test_future.h"
 #include "chrome/browser/ash/crosapi/wallpaper_ash.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
@@ -20,6 +21,7 @@
 #include "chrome/test/base/testing_profile_manager.h"
 #include "chromeos/ash/components/browser_context_helper/annotated_account_id.h"
 #include "chromeos/ash/components/login/login_state/login_state.h"
+#include "chromeos/crosapi/mojom/wallpaper.mojom-forward.h"
 #include "chromeos/crosapi/mojom/wallpaper.mojom.h"
 #include "components/crash/core/common/crash_key.h"
 #include "components/user_manager/scoped_user_manager.h"
@@ -52,6 +54,8 @@
   return jpg_data;
 }
 
+using crosapi::mojom::SetWallpaperResultPtr;
+
 class WallpaperAshTest : public testing::Test {
  public:
   WallpaperAshTest()
@@ -86,6 +90,16 @@
     ash::LoginState::Shutdown();
   }
 
+  SetWallpaperResultPtr SetWallpaper(
+      mojom::WallpaperSettingsPtr wallpaper_settings,
+      const std::string& extension_id,
+      const std::string& extension_name) {
+    base::test::TestFuture<SetWallpaperResultPtr> future;
+    wallpaper_ash_.SetWallpaper(std::move(wallpaper_settings), extension_id,
+                                extension_name, future.GetCallback());
+    return future.Take();
+  }
+
  protected:
   // We need to satisfy DCHECK_CURRENTLY_ON(BrowserThread::UI).
   content::BrowserTaskEnvironment task_environment_;
@@ -105,16 +119,11 @@
   settings->data = CreateJpeg();
   test_wallpaper_controller_.SetCurrentUser(user_manager::StubAccountId());
 
-  base::RunLoop loop;
-  wallpaper_ash_.SetWallpaper(
-      std::move(settings), "extension_id", "extension_name",
-      base::BindLambdaForTesting(
-          [&loop](const crosapi::mojom::SetWallpaperResultPtr result) {
-            ASSERT_TRUE(result->is_thumbnail_data());
-            ASSERT_FALSE(result->get_thumbnail_data().empty());
-            loop.Quit();
-          }));
-  loop.Run();
+  const SetWallpaperResultPtr result =
+      SetWallpaper(std::move(settings), "extension_id", "extension_name");
+
+  ASSERT_TRUE(result->is_thumbnail_data());
+  ASSERT_FALSE(result->get_thumbnail_data().empty());
 
   ASSERT_EQ(1, test_wallpaper_controller_.get_third_party_wallpaper_count());
 }
@@ -125,16 +134,11 @@
   settings->data = CreateJpeg(1, 1);
   test_wallpaper_controller_.SetCurrentUser(user_manager::StubAccountId());
 
-  base::RunLoop loop;
-  wallpaper_ash_.SetWallpaper(
-      std::move(settings), "extension_id", "extension_name",
-      base::BindLambdaForTesting(
-          [&loop](const crosapi::mojom::SetWallpaperResultPtr result) {
-            ASSERT_TRUE(result->is_thumbnail_data());
-            ASSERT_FALSE(result->get_thumbnail_data().empty());
-            loop.Quit();
-          }));
-  loop.Run();
+  const SetWallpaperResultPtr result =
+      SetWallpaper(std::move(settings), "extension_id", "extension_name");
+
+  ASSERT_TRUE(result->is_thumbnail_data());
+  ASSERT_FALSE(result->get_thumbnail_data().empty());
 
   ASSERT_EQ(1, test_wallpaper_controller_.get_third_party_wallpaper_count());
 }
@@ -145,17 +149,11 @@
   test_wallpaper_controller_.SetCurrentUser(user_manager::StubAccountId());
   // Created invalid data by not adding a wallpaper image to the settings data.
 
-  base::RunLoop loop;
-  wallpaper_ash_.SetWallpaper(
-      std::move(settings), "extension_id", "extension_name",
-      base::BindLambdaForTesting(
-          [&loop](const crosapi::mojom::SetWallpaperResultPtr result) {
-            ASSERT_TRUE(result->is_error_message());
-            ASSERT_EQ("Decoding wallpaper data failed.",
-                      result->get_error_message());
-            loop.Quit();
-          }));
-  loop.Run();
+  const SetWallpaperResultPtr result =
+      SetWallpaper(std::move(settings), "extension_id", "extension_name");
+
+  ASSERT_TRUE(result->is_error_message());
+  ASSERT_EQ("Decoding wallpaper data failed.", result->get_error_message());
 
   ASSERT_EQ(0, test_wallpaper_controller_.get_third_party_wallpaper_count());
 }
@@ -166,17 +164,12 @@
   settings->data = CreateJpeg();
   // Setting the wallpaper fails because we haven't set the current user.
 
-  base::RunLoop loop;
-  wallpaper_ash_.SetWallpaper(
-      std::move(settings), "extension_id", "extension_name",
-      base::BindLambdaForTesting(
-          [&loop](const crosapi::mojom::SetWallpaperResultPtr result) {
-            ASSERT_TRUE(result->is_error_message());
-            ASSERT_EQ("Setting the wallpaper failed due to user permissions.",
-                      result->get_error_message());
-            loop.Quit();
-          }));
-  loop.Run();
+  const SetWallpaperResultPtr result =
+      SetWallpaper(std::move(settings), "extension_id", "extension_name");
+
+  ASSERT_TRUE(result->is_error_message());
+  ASSERT_EQ("Setting the wallpaper failed due to user permissions.",
+            result->get_error_message());
 
   ASSERT_EQ(0, test_wallpaper_controller_.get_third_party_wallpaper_count());
 }
@@ -190,21 +183,17 @@
   settings->data = CreateJpeg();
 
   // Invoke SetWallpaper(). It will respond with success.
-  base::RunLoop loop;
-  wallpaper_ash_.SetWallpaper(
-      std::move(settings), "extension_id", "extension_name",
-      base::BindLambdaForTesting(
-          [&loop](const crosapi::mojom::SetWallpaperResultPtr result) {
-            ASSERT_FALSE(result->is_error_message());
-            loop.Quit();
-          }));
+  base::test::TestFuture<SetWallpaperResultPtr> future;
+  wallpaper_ash_.SetWallpaper(std::move(settings), "extension_id",
+                              "extension_name", future.GetCallback());
 
   // Crash key is set when function starts running.
   using crash_reporter::GetCrashKeyValue;
   EXPECT_EQ(GetCrashKeyValue("extension-function-caller-1"), "extension_id");
 
   // Crash key is cleared after function completes.
-  loop.Run();
+  const SetWallpaperResultPtr result = future.Take();
+  ASSERT_FALSE(result->is_error_message());
   EXPECT_EQ(GetCrashKeyValue("extension-function-caller-1"), "");
 }
 
@@ -216,21 +205,17 @@
       crosapi::mojom::WallpaperSettings::New();
 
   // Invoke SetWallpaper(). It will respond with an error.
-  base::RunLoop loop;
-  wallpaper_ash_.SetWallpaper(
-      std::move(settings), "extension_id", "extension_name",
-      base::BindLambdaForTesting(
-          [&loop](const crosapi::mojom::SetWallpaperResultPtr result) {
-            ASSERT_TRUE(result->is_error_message());
-            loop.Quit();
-          }));
+  base::test::TestFuture<SetWallpaperResultPtr> future;
+  wallpaper_ash_.SetWallpaper(std::move(settings), "extension_id",
+                              "extension_name", future.GetCallback());
 
   // Crash key is set when function starts running.
   using crash_reporter::GetCrashKeyValue;
   EXPECT_EQ(GetCrashKeyValue("extension-function-caller-1"), "extension_id");
 
   // Crash key is cleared after function completes.
-  loop.Run();
+  const SetWallpaperResultPtr result = future.Take();
+  ASSERT_TRUE(result->is_error_message());
   EXPECT_EQ(GetCrashKeyValue("extension-function-caller-1"), "");
 }
 
diff --git a/chrome/browser/ash/login/app_mode/test/web_kiosk_browsertest.cc b/chrome/browser/ash/login/app_mode/test/web_kiosk_browsertest.cc
index 996699ee..56335714 100644
--- a/chrome/browser/ash/login/app_mode/test/web_kiosk_browsertest.cc
+++ b/chrome/browser/ash/login/app_mode/test/web_kiosk_browsertest.cc
@@ -39,6 +39,7 @@
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
 #include "components/webapps/browser/install_result_code.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/url_loader_interceptor.h"
@@ -98,6 +99,26 @@
   return success_future.Wait();
 }
 
+Browser::CreateParams CreateNewBrowserParams(Browser* initial_kiosk_browser,
+                                             bool is_popup_browser) {
+  return is_popup_browser
+             ? Browser::CreateParams::CreateForAppPopup(
+                   initial_kiosk_browser->app_name(), /*trusted_source=*/true,
+                   /*window_bounds=*/gfx::Rect(),
+                   initial_kiosk_browser->profile(),
+                   /*user_gesture=*/true)
+             : Browser::CreateParams(initial_kiosk_browser->profile(),
+                                     /*user_gesture=*/true);
+}
+
+Browser* OpenNewBrowser(Browser* initial_kiosk_browser, bool is_popup_browser) {
+  Browser::CreateParams params =
+      CreateNewBrowserParams(initial_kiosk_browser, is_popup_browser);
+  Browser* new_browser = Browser::Create(params);
+  new_browser->window()->Show();
+  return new_browser;
+}
+
 class WebKioskTest : public WebKioskBaseTest {
  public:
   WebKioskTest() = default;
@@ -361,5 +382,73 @@
   EXPECT_FALSE(session->is_shutting_down());
 }
 
+IN_PROC_BROWSER_TEST_F(WebKioskTest,
+                       NewPopupBrowserInKioskNotAllowedByDefault) {
+  InitializeRegularOnlineKiosk();
+  // The initial browser should exist in the web kiosk session.
+  ASSERT_EQ(BrowserList::GetInstance()->size(), 1u);
+  Browser* initial_browser = BrowserList::GetInstance()->get(0);
+  EXPECT_FALSE(initial_browser->profile()->GetPrefs()->GetBoolean(
+      prefs::kNewWindowsInKioskAllowed));
+
+  Browser* new_popup_browser =
+      OpenNewBrowser(initial_browser, /*is_popup_browser=*/true);
+
+  TestBrowserClosedWaiter browser_closed_waiter{new_popup_browser};
+  ASSERT_TRUE(browser_closed_waiter.WaitUntilClosed());
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
+}
+
+IN_PROC_BROWSER_TEST_F(WebKioskTest,
+                       NewRegularBrowserInKioskNotAllowedByDefault) {
+  InitializeRegularOnlineKiosk();
+  // The initial browser should exist in the web kiosk session.
+  ASSERT_EQ(BrowserList::GetInstance()->size(), 1u);
+  Browser* initial_browser = BrowserList::GetInstance()->get(0);
+  EXPECT_FALSE(initial_browser->profile()->GetPrefs()->GetBoolean(
+      prefs::kNewWindowsInKioskAllowed));
+
+  Browser* new_browser =
+      OpenNewBrowser(initial_browser, /*is_popup_browser=*/false);
+
+  TestBrowserClosedWaiter browser_closed_waiter{new_browser};
+  ASSERT_TRUE(browser_closed_waiter.WaitUntilClosed());
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
+}
+
+IN_PROC_BROWSER_TEST_F(WebKioskTest, NewPopupBrowserInKioskAllowedByPolicy) {
+  InitializeRegularOnlineKiosk();
+  // The initial browser should exist in the web kiosk session.
+  ASSERT_EQ(BrowserList::GetInstance()->size(), 1u);
+  Browser* initial_browser = BrowserList::GetInstance()->get(0);
+  KioskSystemSession* session = KioskController::Get().GetKioskSystemSession();
+
+  initial_browser->profile()->GetPrefs()->SetBoolean(
+      prefs::kNewWindowsInKioskAllowed, true);
+  Browser* new_popup_browser =
+      OpenNewBrowser(initial_browser, /*is_popup_browser=*/true);
+
+  EXPECT_FALSE(DidSessionCloseNewWindow(session));
+  ASSERT_NE(new_popup_browser, nullptr);
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 2u);
+}
+
+IN_PROC_BROWSER_TEST_F(WebKioskTest,
+                       NewRegularBrowserInKioskNotAllowedEvenByPolicy) {
+  InitializeRegularOnlineKiosk();
+  // The initial browser should exist in the web kiosk session.
+  ASSERT_EQ(BrowserList::GetInstance()->size(), 1u);
+  Browser* initial_browser = BrowserList::GetInstance()->get(0);
+
+  initial_browser->profile()->GetPrefs()->SetBoolean(
+      prefs::kNewWindowsInKioskAllowed, true);
+  Browser* new_browser =
+      OpenNewBrowser(initial_browser, /*is_popup_browser=*/false);
+
+  TestBrowserClosedWaiter browser_closed_waiter{new_browser};
+  ASSERT_TRUE(browser_closed_waiter.WaitUntilClosed());
+  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
+}
+
 }  // namespace
 }  // namespace ash
diff --git a/chrome/browser/ash/login/oobe_apps_service/oobe_apps_discovery_service.cc b/chrome/browser/ash/login/oobe_apps_service/oobe_apps_discovery_service.cc
index b38fe99..4910f87 100644
--- a/chrome/browser/ash/login/oobe_apps_service/oobe_apps_discovery_service.cc
+++ b/chrome/browser/ash/login/oobe_apps_service/oobe_apps_discovery_service.cc
@@ -64,6 +64,7 @@
   for (oobe::proto::OOBEListResponse::Tag usecase : response->tags()) {
     use_cases_.emplace_back(std::move(usecase));
   }
+  std::sort(use_cases_.begin(), use_cases_.end());
   PropagateResult(std::move(callback_), AppsFetchingResult::kSuccess);
 }
 
diff --git a/chrome/browser/ash/login/oobe_apps_service/oobe_apps_types.h b/chrome/browser/ash/login/oobe_apps_service/oobe_apps_types.h
index dfcf35b1..53451ce 100644
--- a/chrome/browser/ash/login/oobe_apps_service/oobe_apps_types.h
+++ b/chrome/browser/ash/login/oobe_apps_service/oobe_apps_types.h
@@ -33,6 +33,11 @@
 
   const std::string& GetDescription() const;
 
+  // Overloading < operator for sorting.
+  bool operator<(const OOBEDeviceUseCase& obj) const {
+    return order_ < obj.order_;
+  }
+
  private:
   std::string id_;
 
diff --git a/chrome/browser/ash/login/screens/categories_selection_screen.cc b/chrome/browser/ash/login/screens/categories_selection_screen.cc
index fd05a7b..fe74dde53 100644
--- a/chrome/browser/ash/login/screens/categories_selection_screen.cc
+++ b/chrome/browser/ash/login/screens/categories_selection_screen.cc
@@ -20,6 +20,18 @@
 constexpr const char kUserActionNext[] = "next";
 constexpr const char kUserActionSkip[] = "skip";
 
+bool HasBeenSelected(std::string category_id) {
+  PrefService* prefs = ProfileManager::GetActiveUserProfile()->GetPrefs();
+  const base::Value::List& selected_categories =
+      prefs->GetList(prefs::kOobeCategoriesSelected);
+  for (const auto& category : selected_categories) {
+    if (category.GetString() == category_id) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace
 
 // static
@@ -92,24 +104,23 @@
     exit_callback_.Run(Result::kError);
   }
 
-  // PrefService* prefs = ProfileManager::GetActiveUserProfile()->GetPrefs();
-  // const base::Value::List& selected_categories =
-  // prefs->GetList(prefs::kOobeCategoriesSelected);
-
   base::Value::List categories_list;
   for (OOBEDeviceUseCase category : categories) {
+    // Disregard the category with order 0, as it relates to a general,
+    // non-specific use case (oobe_otheres).
+    if (category.GetOrder() == 0) {
+      continue;
+    }
     base::Value::Dict category_dict;
     category_dict.Set("categoryId", base::Value(std::move(category.GetID())));
     category_dict.Set("title", base::Value(std::move(category.GetLabel())));
     category_dict.Set("subtitle",
                       base::Value(std::move(category.GetDescription())));
     category_dict.Set("icon", base::Value(std::move(category.GetImageURL())));
-    category_dict.Set("selected", false);
-    category_dict.Set("order", base::Value(category.GetOrder()));
+    category_dict.Set("selected", HasBeenSelected(category.GetID()));
     categories_list.Append(std::move(category_dict));
   }
-  //  TODO(b/337674429) : need to sort with order as well as pre select old
-  //  value.
+
   base::Value::Dict data;
   data.Set("categories", std::move(categories_list));
   if (view_) {
diff --git a/chrome/browser/ash/policy/login/signin_profile_extensions_policy_browsertest.cc b/chrome/browser/ash/policy/login/signin_profile_extensions_policy_browsertest.cc
index 4d4514dd..1adf434 100644
--- a/chrome/browser/ash/policy/login/signin_profile_extensions_policy_browsertest.cc
+++ b/chrome/browser/ash/policy/login/signin_profile_extensions_policy_browsertest.cc
@@ -30,7 +30,6 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_launcher.h"
 #include "content/public/test/test_utils.h"
-#include "extensions/browser/extension_host_test_helper.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/extension_util.h"
@@ -321,7 +320,7 @@
   EXPECT_TRUE(extension_force_install_mixin_.ForceInstallFromCrx(
       base::PathService::CheckedGet(chrome::DIR_TEST_DATA)
           .AppendASCII(kAllowlistedExtensionCrxPath),
-      ExtensionForceInstallMixin::WaitMode::kBackgroundPageFirstLoad));
+      ExtensionForceInstallMixin::WaitMode::kLoad));
 
   content::StoragePartition* storage_partition_for_app =
       extensions::util::GetStoragePartitionForExtensionId(
diff --git a/chrome/browser/ash/policy/networking/policy_certs_browsertest.cc b/chrome/browser/ash/policy/networking/policy_certs_browsertest.cc
index a61deaa9..2f4739c4 100644
--- a/chrome/browser/ash/policy/networking/policy_certs_browsertest.cc
+++ b/chrome/browser/ash/policy/networking/policy_certs_browsertest.cc
@@ -71,7 +71,6 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/test_utils.h"
 #include "crypto/scoped_test_nss_db.h"
-#include "extensions/browser/extension_host_test_helper.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/test_extension_registry_observer.h"
@@ -638,22 +637,20 @@
     signin_profile_ = GetInitialProfile();
     ASSERT_TRUE(ash::ProfileHelper::IsSigninProfile(signin_profile_));
 
-    extensions::ExtensionHostTestHelper extension_1_observer(
-        signin_profile_, kSigninScreenExtension1);
-    extension_1_observer.RestrictToType(
-        extensions::mojom::ViewType::kExtensionBackgroundPage);
-    extensions::ExtensionHostTestHelper extension_2_observer(
-        signin_profile_, kSigninScreenExtension2);
-    extension_2_observer.RestrictToType(
-        extensions::mojom::ViewType::kExtensionBackgroundPage);
+    extensions::ExtensionRegistry* extension_registry =
+        extensions::ExtensionRegistry::Get(signin_profile_);
+    extensions::TestExtensionRegistryObserver extension_1_observer(
+        extension_registry, kSigninScreenExtension1);
+    extensions::TestExtensionRegistryObserver extension_2_observer(
+        extension_registry, kSigninScreenExtension2);
 
     AddExtensionForForceInstallation(kSigninScreenExtension1,
                                      kSigninScreenExtension1UpdateManifestPath);
     AddExtensionForForceInstallation(kSigninScreenExtension2,
                                      kSigninScreenExtension2UpdateManifestPath);
 
-    extension_1_observer.WaitForHostCompletedFirstLoad();
-    extension_2_observer.WaitForHostCompletedFirstLoad();
+    extension_1_observer.WaitForExtensionLoaded();
+    extension_2_observer.WaitForExtensionLoaded();
   }
 
   content::StoragePartition* GetStoragePartitionForSigninExtension(
diff --git a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
index 1c33654..89d78af2e 100644
--- a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
+++ b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java
@@ -459,19 +459,27 @@
             mValue = value;
         }
 
+        // Creates an Iban instance that is not stored on a server nor locally,
+        // yet. This Iban has type IbanRecordType.UNKNOWN and has neither a
+        // Guid nor an instrumentId.
         @CalledByNative("Iban")
-        public static Iban createLocal(
-                String guid,
-                String label,
-                String nickname,
-                @IbanRecordType int recordType,
-                String value) {
-            assert recordType != IbanRecordType.SERVER_IBAN;
+        public static Iban createEphemeral(String label, String nickname, String value) {
+            return new Iban.Builder()
+                    .setGuid("")
+                    .setLabel(label)
+                    .setNickname(nickname)
+                    .setRecordType(IbanRecordType.UNKNOWN)
+                    .setValue(value)
+                    .build();
+        }
+
+        @CalledByNative("Iban")
+        public static Iban createLocal(String guid, String label, String nickname, String value) {
             return new Iban.Builder()
                     .setGuid(guid)
                     .setLabel(label)
                     .setNickname(nickname)
-                    .setRecordType(recordType)
+                    .setRecordType(IbanRecordType.LOCAL_IBAN)
                     .setValue(value)
                     .build();
         }
@@ -490,6 +498,7 @@
 
         @CalledByNative("Iban")
         public String getGuid() {
+            assert mRecordType != IbanRecordType.SERVER_IBAN;
             return mGuid;
         }
 
@@ -534,12 +543,13 @@
 
             Iban otherIban = (Iban) obj;
 
-            return Objects.equals(mGuid, otherIban.getGuid())
-                    && Objects.equals(mLabel, otherIban.getLabel())
+            return Objects.equals(mLabel, otherIban.getLabel())
                     && Objects.equals(mNickname, otherIban.getNickname())
                     && mRecordType == otherIban.getRecordType()
                     && (mRecordType != IbanRecordType.SERVER_IBAN
                             || Objects.equals(mInstrumentId, otherIban.getInstrumentId()))
+                    && (mRecordType != IbanRecordType.LOCAL_IBAN
+                            || Objects.equals(mGuid, otherIban.getGuid()))
                     && Objects.equals(mValue, otherIban.getValue());
         }
 
diff --git a/chrome/browser/autofill/android/personal_data_manager_android.cc b/chrome/browser/autofill/android/personal_data_manager_android.cc
index 269dee21..22066d5 100644
--- a/chrome/browser/autofill/android/personal_data_manager_android.cc
+++ b/chrome/browser/autofill/android/personal_data_manager_android.cc
@@ -728,21 +728,28 @@
 ScopedJavaLocalRef<jobject>
 PersonalDataManagerAndroid::CreateJavaIbanFromNative(JNIEnv* env,
                                                      const Iban& iban) {
-  if (iban.record_type() == Iban::kServerIban) {
-    return Java_Iban_createServer(
-        env, iban.instrument_id(),
-        ConvertUTF16ToJavaString(env,
-                                 iban.GetIdentifierStringForAutofillDisplay()),
-        ConvertUTF16ToJavaString(env, iban.nickname()),
-        ConvertUTF16ToJavaString(env, iban.GetRawInfo(IBAN_VALUE)));
-  } else {
-    return Java_Iban_createLocal(
-        env, ConvertUTF8ToJavaString(env, iban.guid()),
-        ConvertUTF16ToJavaString(env,
-                                 iban.GetIdentifierStringForAutofillDisplay()),
-        ConvertUTF16ToJavaString(env, iban.nickname()),
-        static_cast<jint>(iban.record_type()),
-        ConvertUTF16ToJavaString(env, iban.GetRawInfo(IBAN_VALUE)));
+  switch (iban.record_type()) {
+    case Iban::kLocalIban:
+      return Java_Iban_createLocal(
+          env, ConvertUTF8ToJavaString(env, iban.guid()),
+          ConvertUTF16ToJavaString(
+              env, iban.GetIdentifierStringForAutofillDisplay()),
+          ConvertUTF16ToJavaString(env, iban.nickname()),
+          ConvertUTF16ToJavaString(env, iban.GetRawInfo(IBAN_VALUE)));
+    case Iban::kServerIban:
+      return Java_Iban_createServer(
+          env, iban.instrument_id(),
+          ConvertUTF16ToJavaString(
+              env, iban.GetIdentifierStringForAutofillDisplay()),
+          ConvertUTF16ToJavaString(env, iban.nickname()),
+          ConvertUTF16ToJavaString(env, iban.GetRawInfo(IBAN_VALUE)));
+    case Iban::kUnknown:
+      return Java_Iban_createEphemeral(
+          env,
+          ConvertUTF16ToJavaString(
+              env, iban.GetIdentifierStringForAutofillDisplay()),
+          ConvertUTF16ToJavaString(env, iban.nickname()),
+          ConvertUTF16ToJavaString(env, iban.GetRawInfo(IBAN_VALUE)));
   }
 }
 
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index a036fb7..7d0b4b30 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -156,7 +156,7 @@
 #include "chrome/browser/android/oom_intervention/oom_intervention_decider.h"
 #include "chrome/browser/android/webapps/webapp_registry.h"
 #include "chrome/browser/offline_pages/offline_page_model_factory.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/android/tab_model/tab_model.h"
 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
 #include "components/cdm/browser/media_drm_storage_impl.h"  // nogncheck crbug.com/1125897
@@ -664,8 +664,7 @@
           ->OnCookiesDeleted();
     }
 
-    if (filter_builder->GetMode() ==
-        BrowsingDataFilterBuilder::Mode::kPreserve) {
+    if (filter_builder->MatchesMostOriginsAndDomains()) {
       auto* privacy_sandbox_settings =
           PrivacySandboxSettingsFactory::GetForProfile(profile_);
       if (privacy_sandbox_settings) {
@@ -678,9 +677,8 @@
       }
 
 #if BUILDFLAG(IS_ANDROID)
-      Java_PackageHash_onCookiesDeleted(
-          base::android::AttachCurrentThread(),
-          ProfileAndroid::FromProfile(profile_)->GetJavaObject());
+      Java_PackageHash_onCookiesDeleted(base::android::AttachCurrentThread(),
+                                        profile_->GetJavaObject());
 #endif
     }
 
@@ -1326,8 +1324,7 @@
 #if BUILDFLAG(ENABLE_DOWNGRADE_PROCESSING)
   //////////////////////////////////////////////////////////////////////////////
   // Remove data for this profile contained in any snapshots.
-  if (remove_mask &&
-      filter_builder->GetMode() == BrowsingDataFilterBuilder::Mode::kPreserve) {
+  if (remove_mask && filter_builder->MatchesMostOriginsAndDomains()) {
     base::ThreadPool::PostTaskAndReply(
         FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
         base::BindOnce(&downgrade::RemoveDataForProfile, delete_begin_,
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index 4fd6743..4064b92 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -23,6 +23,7 @@
 #include "base/memory/raw_ref.h"
 #include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
+#include "base/scoped_observation.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
@@ -149,6 +150,7 @@
 #include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h"
 #include "components/privacy_sandbox/privacy_sandbox_attestations/scoped_privacy_sandbox_attestations.h"
 #include "components/privacy_sandbox/privacy_sandbox_settings.h"
+#include "components/privacy_sandbox/privacy_sandbox_test_util.h"
 #include "components/reading_list/core/mock_reading_list_model_observer.h"
 #include "components/reading_list/core/reading_list_model.h"
 #include "components/safe_browsing/core/browser/verdict_cache_manager.h"
@@ -181,6 +183,7 @@
 #include "net/base/network_anonymization_key.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_access_result.h"
+#include "net/cookies/cookie_partition_key_collection.h"
 #include "net/http/http_auth.h"
 #include "net/http/http_auth_cache.h"
 #include "net/http/http_transaction_factory.h"
@@ -4300,6 +4303,56 @@
 }
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+// When most cookies are cleared, PrivacySandboxSettings should call the
+// OnTopicsDataAccessibleSinceUpdated() method of its observers.
+TEST_F(ChromeBrowsingDataRemoverDelegateTest,
+       Call_OnTopicsDataAccessibleSinceUpdated_WhenClearingMostCookies) {
+  privacy_sandbox::PrivacySandboxSettings* settings =
+      PrivacySandboxSettingsFactory::GetForProfile(GetProfile());
+  privacy_sandbox_test_util::MockPrivacySandboxObserver observer;
+  base::ScopedObservation<privacy_sandbox::PrivacySandboxSettings,
+                          privacy_sandbox::PrivacySandboxSettings::Observer>
+      obs(&observer);
+  obs.Observe(settings);
+
+  EXPECT_CALL(observer, OnTopicsDataAccessibleSinceUpdated()).Times(1);
+
+  std::unique_ptr<BrowsingDataFilterBuilder> filter_builder =
+      BrowsingDataFilterBuilder::Create(
+          BrowsingDataFilterBuilder::Mode::kPreserve);
+  filter_builder->AddRegisterableDomain("example.test");
+  ASSERT_TRUE(filter_builder->MatchesMostOriginsAndDomains());
+  BlockUntilOriginDataRemoved(base::Time::Min(), base::Time::Max(),
+                              content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+                              std::move(filter_builder));
+}
+
+// If only some cookies are cleared, PrivacySandboxSettings should NOT call the
+// OnTopicsDataAccessibleSinceUpdated() method of its observers.
+TEST_F(
+    ChromeBrowsingDataRemoverDelegateTest,
+    DontCall_OnTopicsDataAccessibleSinceUpdated_WhenOnlyClearingPartitionedCookies) {
+  privacy_sandbox::PrivacySandboxSettings* settings =
+      PrivacySandboxSettingsFactory::GetForProfile(GetProfile());
+  privacy_sandbox_test_util::MockPrivacySandboxObserver observer;
+  base::ScopedObservation<privacy_sandbox::PrivacySandboxSettings,
+                          privacy_sandbox::PrivacySandboxSettings::Observer>
+      obs(&observer);
+  obs.Observe(settings);
+
+  EXPECT_CALL(observer, OnTopicsDataAccessibleSinceUpdated()).Times(0);
+
+  // Create a filter builder that deletes only partitioned cookies.
+  std::unique_ptr<BrowsingDataFilterBuilder> filter_builder =
+      BrowsingDataFilterBuilder::Create(
+          BrowsingDataFilterBuilder::Mode::kPreserve);
+  filter_builder->SetPartitionedCookiesOnly(true);
+  ASSERT_FALSE(filter_builder->MatchesMostOriginsAndDomains());
+  BlockUntilOriginDataRemoved(base::Time::Min(), base::Time::Max(),
+                              content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+                              std::move(filter_builder));
+}
+
 class ChromeBrowsingDataRemoverDelegateOriginTrialsTest
     : public ChromeBrowsingDataRemoverDelegateTest {
  public:
@@ -4572,7 +4625,8 @@
   }
 }
 
-TEST_F(ChromeBrowsingDataRemoverDelegateTpcdMetadataTest, ResetAllCohort_PreserveMode) {
+TEST_F(ChromeBrowsingDataRemoverDelegateTpcdMetadataTest,
+       ResetAllCohort_PreserveSome) {
   auto tester = RemoveTpcdMetadataCohortsTester(GetProfile());
 
   const std::string primary_pattern_spec = "https://example1.com";
@@ -4627,12 +4681,12 @@
         content_settings::mojom::TpcdMetadataCohort::GRACE_PERIOD_FORCED_ON);
   }
 
-  // Apply deletion of cookies with preservation of select URL.
-  // NOTE: This is still expected to reset all cohorts.
+  // Apply deletion of all cookies.
   std::unique_ptr<BrowsingDataFilterBuilder> filter(
       BrowsingDataFilterBuilder::Create(
           BrowsingDataFilterBuilder::Mode::kPreserve));
   filter->AddRegisterableDomain(GURL(primary_pattern_spec).host());
+  ASSERT_TRUE(filter->MatchesMostOriginsAndDomains());
   BlockUntilOriginDataRemoved(base::Time(), base::Time::Max(),
                               content::BrowsingDataRemover::DATA_TYPE_COOKIES,
                               std::move(filter));
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 8e02930..498fa45 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -4136,10 +4136,8 @@
   web_prefs->always_show_focus =
       prefs->GetBoolean(ash::prefs::kAccessibilityFocusHighlightEnabled);
 #else
-  if (features::IsAccessibilityFocusHighlightEnabled()) {
-    web_prefs->always_show_focus =
-        prefs->GetBoolean(prefs::kAccessibilityFocusHighlightEnabled);
-  }
+  web_prefs->always_show_focus =
+      prefs->GetBoolean(prefs::kAccessibilityFocusHighlightEnabled);
 #endif
 
 #if BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/commerce/price_tracking/android/price_tracking_notification_bridge.cc b/chrome/browser/commerce/price_tracking/android/price_tracking_notification_bridge.cc
index f9b0f16..de1e663 100644
--- a/chrome/browser/commerce/price_tracking/android/price_tracking_notification_bridge.cc
+++ b/chrome/browser/commerce/price_tracking/android/price_tracking_notification_bridge.cc
@@ -8,7 +8,6 @@
 #include "base/memory/ptr_util.h"
 #include "chrome/browser/commerce/price_tracking/android/jni_headers/PriceTrackingNotificationBridge_jni.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "content/public/browser/browser_context.h"
 
 using OptimizationType = optimization_guide::proto::OptimizationType;
@@ -31,11 +30,10 @@
 PriceTrackingNotificationBridge::PriceTrackingNotificationBridge(
     Profile* profile) {
   JNIEnv* env = jni_zero::AttachCurrentThread();
-  java_obj_.Reset(env,
-                  Java_PriceTrackingNotificationBridge_create(
-                      env, reinterpret_cast<intptr_t>(this),
-                      ProfileAndroid::FromProfile(profile)->GetJavaObject())
-                      .obj());
+  java_obj_.Reset(
+      env, Java_PriceTrackingNotificationBridge_create(
+               env, reinterpret_cast<intptr_t>(this), profile->GetJavaObject())
+               .obj());
 }
 
 PriceTrackingNotificationBridge::~PriceTrackingNotificationBridge() = default;
diff --git a/chrome/browser/component_updater/recovery_component_installer.cc b/chrome/browser/component_updater/recovery_component_installer.cc
index 74c3e5f7..1ee83f7 100644
--- a/chrome/browser/component_updater/recovery_component_installer.cc
+++ b/chrome/browser/component_updater/recovery_component_installer.cc
@@ -314,9 +314,7 @@
 RecoveryComponentInstaller::RecoveryComponentInstaller(
     const base::Version& version,
     PrefService* prefs)
-    : current_version_(version), prefs_(prefs) {
-  DCHECK(version.IsValid());
-}
+    : current_version_(version), prefs_(prefs) {}
 
 void RecoveryComponentInstaller::OnUpdateError(int error) {
   NOTREACHED_IN_MIGRATION() << "Recovery component update error: " << error;
diff --git a/chrome/browser/component_updater/recovery_improved_component_installer.cc b/chrome/browser/component_updater/recovery_improved_component_installer.cc
index 4369abfb..f0f40b5 100644
--- a/chrome/browser/component_updater/recovery_improved_component_installer.cc
+++ b/chrome/browser/component_updater/recovery_improved_component_installer.cc
@@ -86,7 +86,6 @@
 void RecoveryComponentActionHandler::UnpackComplete(
     const update_client::Unpacker::Result& result) {
   if (result.error != update_client::UnpackerError::kNone) {
-    DCHECK(!base::DirectoryExists(result.unpack_path));
     main_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(std::move(callback_), false,
diff --git a/chrome/browser/component_updater/updater_state.cc b/chrome/browser/component_updater/updater_state.cc
index fd55a4b..96e44c5e 100644
--- a/chrome/browser/component_updater/updater_state.cc
+++ b/chrome/browser/component_updater/updater_state.cc
@@ -144,7 +144,7 @@
   state.is_autoupdate_check_enabled = IsAutoupdateCheckEnabled();
   state.update_policy = [this] {
     const int update_policy = GetUpdatePolicy();
-    DCHECK((update_policy >= 0 && update_policy <= 3) || update_policy == -1);
+    CHECK((update_policy >= 0 && update_policy <= 3) || update_policy == -1);
     return update_policy;
   }();
   return state;
@@ -216,7 +216,7 @@
     val = "1344";  // 2*28 days in hours.
   }
 
-  DCHECK(!val.empty());
+  CHECK(!val.empty());
   return val;
 }
 
diff --git a/chrome/browser/component_updater/updater_state_mac.mm b/chrome/browser/component_updater/updater_state_mac.mm
index 4b73439..b2fa863 100644
--- a/chrome/browser/component_updater/updater_state_mac.mm
+++ b/chrome/browser/component_updater/updater_state_mac.mm
@@ -73,17 +73,15 @@
 
 base::Version UpdaterState::StateReaderKeystone::GetUpdaterVersion(
     bool /*is_machine*/) const {
-  // System Keystone trumps user one, so check this one first
+  // System Keystone takes precedence over user one, so check this one first.
   base::FilePath local_library;
-  bool success =
-      base::apple::GetLocalDirectory(NSLibraryDirectory, &local_library);
-  DCHECK(success);
-  base::FilePath system_bundle_plist = local_library.Append(kKeystonePlist);
-  base::Version system_keystone = GetVersionFromPlist(system_bundle_plist);
-  if (system_keystone.IsValid()) {
-    return system_keystone;
+  if (base::apple::GetLocalDirectory(NSLibraryDirectory, &local_library)) {
+    base::FilePath system_bundle_plist = local_library.Append(kKeystonePlist);
+    base::Version system_keystone = GetVersionFromPlist(system_bundle_plist);
+    if (system_keystone.IsValid()) {
+      return system_keystone;
+    }
   }
-
   base::FilePath user_bundle_plist =
       base::apple::GetUserLibraryPath().Append(kKeystonePlist);
   return GetVersionFromPlist(user_bundle_plist);
diff --git a/chrome/browser/data_sharing/data_sharing_ui_delegate_android.cc b/chrome/browser/data_sharing/data_sharing_ui_delegate_android.cc
index 6e0989e7..9c97316 100644
--- a/chrome/browser/data_sharing/data_sharing_ui_delegate_android.cc
+++ b/chrome/browser/data_sharing/data_sharing_ui_delegate_android.cc
@@ -6,7 +6,7 @@
 
 #include "base/android/jni_string.h"
 #include "chrome/browser/data_sharing/jni_headers/DataSharingUIDelegateAndroid_jni.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "url/android/gurl_android.h"
 #include "url/gurl.h"
 
@@ -14,7 +14,7 @@
 
 DataSharingUIDelegateAndroid::DataSharingUIDelegateAndroid(Profile* profile) {
   JNIEnv* env = base::android::AttachCurrentThread();
-  auto j_profile = ProfileAndroid::FromProfile(profile)->GetJavaObject();
+  auto j_profile = profile->GetJavaObject();
   java_obj_.Reset(
       env, Java_DataSharingUIDelegateAndroid_create(env, j_profile).obj());
 }
diff --git a/chrome/browser/download/android/download_controller.cc b/chrome/browser/download/android/download_controller.cc
index 658c112..600264144 100644
--- a/chrome/browser/download/android/download_controller.cc
+++ b/chrome/browser/download/android/download_controller.cc
@@ -33,7 +33,6 @@
 #include "chrome/browser/offline_pages/android/offline_page_bridge.h"
 #include "chrome/browser/permissions/permission_update_message_controller_android.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/ui/android/tab_model/tab_model.h"
 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
 #include "chrome/grit/branded_strings.h"
diff --git a/chrome/browser/download/android/download_dialog_bridge.cc b/chrome/browser/download/android/download_dialog_bridge.cc
index 7ead77b..31ab760d 100644
--- a/chrome/browser/download/android/download_dialog_bridge.cc
+++ b/chrome/browser/download/android/download_dialog_bridge.cc
@@ -11,7 +11,6 @@
 #include "chrome/browser/download/android/download_controller.h"
 #include "chrome/browser/download/android/jni_headers/DownloadDialogBridge_jni.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/pref_names.h"
 #include "components/download/public/common/download_features.h"
@@ -86,7 +85,7 @@
       static_cast<int>(dialog_type),
       base::android::ConvertUTF8ToJavaString(env,
                                              suggested_path.AsUTF8Unsafe()),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject());
+      profile->GetJavaObject());
 }
 
 void DownloadDialogBridge::OnComplete(
diff --git a/chrome/browser/download/android/items/offline_content_aggregator_factory_android.cc b/chrome/browser/download/android/items/offline_content_aggregator_factory_android.cc
index d4a2be8..9811d6e 100644
--- a/chrome/browser/download/android/items/offline_content_aggregator_factory_android.cc
+++ b/chrome/browser/download/android/items/offline_content_aggregator_factory_android.cc
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
+
 #include "base/android/jni_android.h"
 #include "base/android/scoped_java_ref.h"
 #include "chrome/browser/android/profile_key_util.h"
 #include "chrome/browser/download/android/jni_headers/OfflineContentAggregatorFactory_jni.h"
-#include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/profiles/profile_key.h"
 #include "components/offline_items_collection/core/android/offline_content_aggregator_bridge.h"
 #include "components/offline_items_collection/core/offline_content_aggregator.h"
diff --git a/chrome/browser/download/android/open_download_dialog_bridge.cc b/chrome/browser/download/android/open_download_dialog_bridge.cc
index 21304b40..c988897 100644
--- a/chrome/browser/download/android/open_download_dialog_bridge.cc
+++ b/chrome/browser/download/android/open_download_dialog_bridge.cc
@@ -18,7 +18,6 @@
 #include "chrome/browser/download/android/download_dialog_utils.h"
 #include "chrome/browser/download/android/open_download_dialog_bridge_delegate.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/download_item_utils.h"
@@ -44,8 +43,7 @@
                                     const std::string& download_guid) {
   JNIEnv* env = base::android::AttachCurrentThread();
   Java_OpenDownloadDialogBridge_showDialog(
-      env, java_object_, ProfileAndroid::FromProfile(profile)->GetJavaObject(),
-      download_guid);
+      env, java_object_, profile->GetJavaObject(), download_guid);
 }
 
 void OpenDownloadDialogBridge::OnConfirmed(JNIEnv* env,
diff --git a/chrome/browser/enterprise/data_controls/chrome_rules_service_unittest.cc b/chrome/browser/enterprise/data_controls/chrome_rules_service_unittest.cc
index ddac0d70..5569eff 100644
--- a/chrome/browser/enterprise/data_controls/chrome_rules_service_unittest.cc
+++ b/chrome/browser/enterprise/data_controls/chrome_rules_service_unittest.cc
@@ -24,13 +24,26 @@
 
 class DataControlsRulesServiceTest : public testing::Test {
  public:
-  explicit DataControlsRulesServiceTest(bool feature_enabled = true)
+  explicit DataControlsRulesServiceTest(bool desktop_feature_enabled = true,
+                                        bool screenshot_feature_enabled = true)
       : profile_manager_(TestingBrowserProcess::GetGlobal()) {
-    if (feature_enabled) {
-      scoped_features_.InitAndEnableFeature(kEnableDesktopDataControls);
+    std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
+
+    if (desktop_feature_enabled) {
+      enabled_features.push_back(kEnableDesktopDataControls);
     } else {
-      scoped_features_.InitAndDisableFeature(kEnableDesktopDataControls);
+      disabled_features.push_back(kEnableDesktopDataControls);
     }
+
+    if (screenshot_feature_enabled) {
+      enabled_features.push_back(kEnableScreenshotProtection);
+    } else {
+      disabled_features.push_back(kEnableScreenshotProtection);
+    }
+
+    scoped_features_.InitWithFeatures(enabled_features, disabled_features);
+
     EXPECT_TRUE(profile_manager_.SetUp());
     profile_ = profile_manager_.CreateTestingProfile("test-user-1");
     other_profile_ = profile_manager_.CreateTestingProfile("test-user-2");
@@ -137,16 +150,102 @@
   std::unique_ptr<content::WebContents> incognito_web_contents_;
 };
 
-class DataControlsRulesServiceFeatureDisabledTest
+class DataControlsRulesServiceDesktopFeatureDisabledTest
     : public DataControlsRulesServiceTest {
  public:
-  DataControlsRulesServiceFeatureDisabledTest()
-      : DataControlsRulesServiceTest(false) {}
+  DataControlsRulesServiceDesktopFeatureDisabledTest()
+      : DataControlsRulesServiceTest(false, true) {}
+};
+
+class DataControlsRulesServiceScreenshotFeatureDisabledTest
+    : public DataControlsRulesServiceTest {
+ public:
+  DataControlsRulesServiceScreenshotFeatureDisabledTest()
+      : DataControlsRulesServiceTest(true, false) {}
+};
+
+class DataControlsRulesServiceAllFeaturesDisabledTest
+    : public DataControlsRulesServiceTest {
+ public:
+  DataControlsRulesServiceAllFeaturesDisabledTest()
+      : DataControlsRulesServiceTest(false, false) {}
 };
 
 }  // namespace
 
-TEST_F(DataControlsRulesServiceFeatureDisabledTest, NoVerdicts) {
+TEST_F(DataControlsRulesServiceDesktopFeatureDisabledTest,
+       NoVerdictsForDesktopRestrictions) {
+  SetDataControls(profile()->GetPrefs(), {R"({
+                    "name": "block",
+                    "rule_id": "1234",
+                    "sources": {
+                      "urls": ["google.com"]
+                    },
+                    "restrictions": [
+                      {"class": "PRINTING", "level": "BLOCK"},
+                      {"class": "CLIPBOARD", "level": "BLOCK"},
+                      {"class": "SCREENSHOT", "level": "BLOCK"}
+                    ]
+                  })"});
+  ExpectNoVerdict(ChromeRulesServiceFactory::GetInstance()
+                      ->GetForBrowserContext(profile())
+                      ->GetPrintVerdict(google_url()));
+  ExpectNoVerdict(ChromeRulesServiceFactory::GetInstance()
+                      ->GetForBrowserContext(profile())
+                      ->GetPasteVerdict(
+                          /*source*/ google_url_endpoint(),
+                          /*destination*/ empty_endpoint(),
+                          /*metadata*/ {}));
+  ExpectNoVerdict(ChromeRulesServiceFactory::GetInstance()
+                      ->GetForBrowserContext(profile())
+                      ->GetCopyToOSClipboardVerdict(
+                          /*source*/ google_url()));
+  ExpectNoVerdict(ChromeRulesServiceFactory::GetInstance()
+                      ->GetForBrowserContext(profile())
+                      ->GetCopyRestrictedBySourceVerdict(
+                          /*source*/ google_url()));
+  EXPECT_TRUE(ChromeRulesServiceFactory::GetInstance()
+                  ->GetForBrowserContext(profile())
+                  ->BlockScreenshots(google_url()));
+}
+
+TEST_F(DataControlsRulesServiceScreenshotFeatureDisabledTest,
+       NoVerdictsForScreenshotRestriction) {
+  SetDataControls(profile()->GetPrefs(), {R"({
+                    "name": "block",
+                    "rule_id": "1234",
+                    "sources": {
+                      "urls": ["google.com"]
+                    },
+                    "restrictions": [
+                      {"class": "PRINTING", "level": "BLOCK"},
+                      {"class": "CLIPBOARD", "level": "BLOCK"},
+                      {"class": "SCREENSHOT", "level": "BLOCK"}
+                    ]
+                  })"});
+  ExpectBlockVerdict(ChromeRulesServiceFactory::GetInstance()
+                         ->GetForBrowserContext(profile())
+                         ->GetPrintVerdict(google_url()));
+  ExpectBlockVerdict(ChromeRulesServiceFactory::GetInstance()
+                         ->GetForBrowserContext(profile())
+                         ->GetPasteVerdict(
+                             /*source*/ google_url_endpoint(),
+                             /*destination*/ empty_endpoint(),
+                             /*metadata*/ {}));
+  ExpectBlockVerdict(ChromeRulesServiceFactory::GetInstance()
+                         ->GetForBrowserContext(profile())
+                         ->GetCopyToOSClipboardVerdict(
+                             /*source*/ google_url()));
+  ExpectBlockVerdict(ChromeRulesServiceFactory::GetInstance()
+                         ->GetForBrowserContext(profile())
+                         ->GetCopyRestrictedBySourceVerdict(
+                             /*source*/ google_url()));
+  EXPECT_FALSE(ChromeRulesServiceFactory::GetInstance()
+                   ->GetForBrowserContext(profile())
+                   ->BlockScreenshots(google_url()));
+}
+
+TEST_F(DataControlsRulesServiceAllFeaturesDisabledTest, NoVerdicts) {
   SetDataControls(profile()->GetPrefs(), {R"({
                     "name": "block",
                     "rule_id": "1234",
diff --git a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
index bef1d82..d28601a 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer.cc
@@ -153,13 +153,13 @@
       sessions::SessionTabHelper::IdForTab(web_contents));
 }
 
-bool IsDesktopDataControlsEnabled() {
+bool IsScreenshotProtectionEnabled() {
   return base::FeatureList::IsEnabled(
-      data_controls::kEnableDesktopDataControls);
+      data_controls::kEnableScreenshotProtection);
 }
 
 bool IsDataProtectionEnabled(Profile* profile) {
-  return IsEnterpriseLookupEnabled(profile) || IsDesktopDataControlsEnabled();
+  return IsEnterpriseLookupEnabled(profile) || IsScreenshotProtectionEnabled();
 }
 
 std::string GetIdentifier(content::BrowserContext* browser_context) {
@@ -239,7 +239,7 @@
 
   std::string identifier = GetIdentifier(profile);
 
-  if (IsDesktopDataControlsEnabled()) {
+  if (IsScreenshotProtectionEnabled()) {
     DataProtectionPageUserData::UpdateScreenshotState(
         GetPageFromWebContents(web_contents), identifier,
         IsScreenshotAllowedByDataControls(profile,
diff --git a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer_unittest.cc b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer_unittest.cc
index f7420dd..6d1de36 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_navigation_observer_unittest.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_navigation_observer_unittest.cc
@@ -148,7 +148,7 @@
     content::RenderViewHostTestHarness::SetUp();
 
     scoped_features_.InitAndEnableFeature(
-        data_controls::kEnableDesktopDataControls);
+        data_controls::kEnableScreenshotProtection);
 
     profile_manager_ = std::make_unique<TestingProfileManager>(
         TestingBrowserProcess::GetGlobal());
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_features.cc b/chrome/browser/enterprise/platform_auth/platform_auth_features.cc
index c29077c..5bda314b 100644
--- a/chrome/browser/enterprise/platform_auth/platform_auth_features.cc
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_features.cc
@@ -8,4 +8,8 @@
 
 namespace enterprise_auth {
 
+BASE_FEATURE(kEnableExtensibleEnterpriseSSO,
+             "EnableExtensibleEnterpriseSSO",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace enterprise_auth
diff --git a/chrome/browser/enterprise/platform_auth/platform_auth_features.h b/chrome/browser/enterprise/platform_auth/platform_auth_features.h
index d956e967..4439e05 100644
--- a/chrome/browser/enterprise/platform_auth/platform_auth_features.h
+++ b/chrome/browser/enterprise/platform_auth/platform_auth_features.h
@@ -10,6 +10,8 @@
 
 namespace enterprise_auth {
 
+BASE_DECLARE_FEATURE(kEnableExtensibleEnterpriseSSO);
+
 }  // namespace enterprise_auth
 
 #endif  // CHROME_BROWSER_ENTERPRISE_PLATFORM_AUTH_PLATFORM_AUTH_FEATURES_H_
diff --git a/chrome/browser/feedback/android/family_info_feedback_source_unittest.cc b/chrome/browser/feedback/android/family_info_feedback_source_unittest.cc
index 2e83937..e16b8dc8 100644
--- a/chrome/browser/feedback/android/family_info_feedback_source_unittest.cc
+++ b/chrome/browser/feedback/android/family_info_feedback_source_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/notreached.h"
 #include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/signin/chrome_signin_client_factory.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
 #include "chrome/browser/signin/test_signin_client_builder.h"
@@ -119,11 +118,8 @@
  private:
   // Creates a Java instance of FamilyInfoFeedbackSource.
   base::android::ScopedJavaLocalRef<jobject> CreateJavaObjectForTesting() {
-    ProfileAndroid* profile_android =
-        ProfileAndroid::FromProfile(profile_.get());
     return Java_FamilyInfoFeedbackSourceTestBridge_createFamilyInfoFeedbackSource(
-        env_, base::android::JavaParamRef<jobject>(
-                  env_, profile_android->GetJavaObject().Release()));
+        env_, profile_.get()->GetJavaObject());
   }
 
   content::BrowserTaskEnvironment task_environment_;
@@ -251,11 +247,8 @@
  private:
   // Creates a Java instance of FamilyInfoFeedbackSource.
   base::android::ScopedJavaLocalRef<jobject> CreateJavaObjectForTesting() {
-    ProfileAndroid* profile_android =
-        ProfileAndroid::FromProfile(profile_.get());
     return Java_FamilyInfoFeedbackSourceTestBridge_createFamilyInfoFeedbackSource(
-        env_, base::android::JavaParamRef<jobject>(
-                  env_, profile_android->GetJavaObject().Release()));
+        env_, profile_.get()->GetJavaObject());
   }
 
   content::BrowserTaskEnvironment task_environment_;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 14c6c8d..0a316671 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2959,6 +2959,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "enable-extensible-enterprise-sso",
+    "owners": [ "ydago@chromium.org", "cbe-magic@google.com" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "enable-external-display-hdr10",
     "owners": [ "sashamcintosh@chromium.org", "chromeos-gfx@google.com" ],
     "expiry_milestone": 140
@@ -7570,11 +7575,6 @@
     "expiry_milestone": 92
   },
   {
-    "name": "screencast-v2",
-    "owners": [ "dorianbrandon@google.com","cros-edu-team@google.com"],
-    "expiry_milestone": 123
-  },
-  {
     "name": "screenshots-for-android-v2",
     "owners": [ "kenok@google.com", "addisonphelps@google.com", "jeffreycohen@chromium.org" ],
     "expiry_milestone": 115
@@ -8660,7 +8660,7 @@
       "etuck@google.com",
       "cros-status-area-eng@google.com"
     ],
-    "expiry_milestone": 126
+    "expiry_milestone": 130
   },
   {
     "name": "vc-light-intensity",
@@ -8792,6 +8792,11 @@
     "expiry_milestone": 129
   },
   {
+    "name": "web-authentication-enclave-authenticator",
+    "owners": [ "kenrb@chromium.org", "chrome-webauthn@google.com" ],
+    "expiry_milestone": 129
+  },
+  {
     "name": "web-authentication-permit-enterprise-attestation",
     "owners": [ "chrome-webauthn@google.com" ],
     // This flag lets end users enroll security keys with an enterprise that
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index b2c8c50..572c2fc 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3637,6 +3637,14 @@
 const char kWallpaperPerDeskDescription[] =
     "Allow users to set different wallpapers on each of their active desks";
 
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
+const char kWebAuthnEnclaveAuthenticatorName[] =
+    "Enable the cloud enclave authenticator for GPM passkeys";
+const char kWebAuthnEnclaveAuthenticatorDescription[] =
+    "Allow users to create and use Google Password Manager passkeys using a "
+    "cloud-based authenticator service.";
+#endif
+
 const char kWebBluetoothName[] = "Web Bluetooth";
 const char kWebBluetoothDescription[] =
     "Enables the Web Bluetooth API on platforms without official support";
@@ -5424,6 +5432,10 @@
     "that calls the PPD API.";
 #endif  // BUILDFLAG(ENABLE_PRINTING)
 
+const char kEnableExtensibleEnterpriseSSOName[] = "Extensible Enterprise SSO";
+const char kEnableExtensibleEnterpriseSSODescription[] =
+    "Enables support for extensible enterprise SSO in Chrome";
+
 const char kImmersiveFullscreenName[] = "Immersive Fullscreen Toolbar";
 const char kImmersiveFullscreenDescription[] =
     "Automatically hide and show the toolbar in fullscreen.";
@@ -7174,9 +7186,6 @@
 const char kScalableIphDebugDescription[] =
     "Enables debug feature of Scalable Iph";
 
-const char kScreencastV2Name[] = "Screencast V2";
-const char kScreencastV2Description[] = "Enable V2 features for Screencast app";
-
 const char kSeaPenName[] = "SeaPen";
 const char kSeaPenDescription[] = "Enable SeaPen Wallpaper";
 
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 2c71438..2e5a394 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2114,6 +2114,11 @@
 extern const char kWallpaperPerDeskName[];
 extern const char kWallpaperPerDeskDescription[];
 
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
+extern const char kWebAuthnEnclaveAuthenticatorName[];
+extern const char kWebAuthnEnclaveAuthenticatorDescription[];
+#endif
+
 extern const char kWebBluetoothName[];
 extern const char kWebBluetoothDescription[];
 
@@ -3154,6 +3159,9 @@
 extern const char kCupsIppPrintingBackendDescription[];
 #endif  // BUILDFLAG(ENABLE_PRINTING)
 
+extern const char kEnableExtensibleEnterpriseSSOName[];
+extern const char kEnableExtensibleEnterpriseSSODescription[];
+
 extern const char kImmersiveFullscreenName[];
 extern const char kImmersiveFullscreenDescription[];
 
@@ -4156,9 +4164,6 @@
 extern const char kScalableIphDebugName[];
 extern const char kScalableIphDebugDescription[];
 
-extern const char kScreencastV2Name[];
-extern const char kScreencastV2Description[];
-
 extern const char kSealKeyName[];
 extern const char kSealKeyDescription[];
 
diff --git a/chrome/browser/language/android/language_bridge.cc b/chrome/browser/language/android/language_bridge.cc
index 4359cc9c3d..2c18282 100644
--- a/chrome/browser/language/android/language_bridge.cc
+++ b/chrome/browser/language/android/language_bridge.cc
@@ -9,7 +9,6 @@
 #include "chrome/browser/language/android/jni_headers/LanguageBridge_jni.h"
 #include "chrome/browser/language/language_model_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "components/language/core/browser/language_model.h"
 #include "components/language/core/browser/language_model_manager.h"
 #include "components/language/core/browser/language_prefs.h"
@@ -22,7 +21,7 @@
 namespace {
 
 PrefService* GetPrefService(const base::android::JavaRef<jobject>& j_profile) {
-  return ProfileAndroid::FromProfileAndroid(j_profile)->GetPrefs();
+  return Profile::FromJavaObject(j_profile)->GetPrefs();
 }
 
 }  // namespace
diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
index 60320e94..82fbfafb 100644
--- a/chrome/browser/net/profile_network_context_service.cc
+++ b/chrome/browser/net/profile_network_context_service.cc
@@ -348,11 +348,6 @@
       base::BindRepeating(&ProfileNetworkContextService::
                               UpdateCorsNonWildcardRequestHeadersSupport,
                           base::Unretained(this)));
-  pref_change_registrar_.Add(
-      prefs::kBlockTruncatedCookies,
-      base::BindRepeating(
-          &ProfileNetworkContextService::OnTruncatedCookieBlockingChanged,
-          base::Unretained(this)));
 }
 
 ProfileNetworkContextService::~ProfileNetworkContextService() = default;
@@ -462,20 +457,6 @@
       });
 }
 
-void ProfileNetworkContextService::OnTruncatedCookieBlockingChanged() {
-  const bool block_truncated_cookies =
-      profile_->GetPrefs()->GetBoolean(prefs::kBlockTruncatedCookies);
-
-  profile_->ForEachLoadedStoragePartition(
-      [&](content::StoragePartition* storage_partition) {
-        // Update the main CookieManager's CookieSettings object to block
-        // truncated cookies, and since this is shared with all of the
-        // RestrictedCookieManager instances, those will get the change as well.
-        storage_partition->GetCookieManagerForBrowserProcess()
-            ->BlockTruncatedCookies(block_truncated_cookies);
-      });
-}
-
 std::string ProfileNetworkContextService::ComputeAcceptLanguage() const {
   // If reduce accept language is enabled, only return the first language
   // without expanding the language list.
@@ -818,9 +799,6 @@
   out->cookie_access_delegate_type =
       network::mojom::CookieAccessDelegateType::USE_CONTENT_SETTINGS;
 
-  out->block_truncated_cookies =
-      profile->GetPrefs()->GetBoolean(prefs::kBlockTruncatedCookies);
-
   out->mitigations_enabled_for_3pcd =
       cookie_settings.MitigationsEnabledFor3pcd();
 
diff --git a/chrome/browser/net/profile_network_context_service.h b/chrome/browser/net/profile_network_context_service.h
index b11868c..76e8a50f 100644
--- a/chrome/browser/net/profile_network_context_service.h
+++ b/chrome/browser/net/profile_network_context_service.h
@@ -167,8 +167,6 @@
 
   void UpdateCorsNonWildcardRequestHeadersSupport();
 
-  void OnTruncatedCookieBlockingChanged();
-
   // Creates parameters for the NetworkContext. Use |in_memory| instead of
   // |profile_->IsOffTheRecord()| because sometimes normal profiles want off the
   // record partitions (e.g. for webview tag).
diff --git a/chrome/browser/notifications/notification_platform_bridge_android.cc b/chrome/browser/notifications/notification_platform_bridge_android.cc
index f82ea6a..20a7db4 100644
--- a/chrome/browser/notifications/notification_platform_bridge_android.cc
+++ b/chrome/browser/notifications/notification_platform_bridge_android.cc
@@ -22,7 +22,7 @@
 #include "chrome/browser/notifications/notification_common.h"
 #include "chrome/browser/notifications/notification_display_service_impl.h"
 #include "chrome/browser/notifications/platform_notification_service_impl.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/notifications/notification_constants.h"
@@ -281,8 +281,7 @@
   if (!scope_url.is_valid())
     scope_url = origin_url;
 
-  ScopedJavaLocalRef<jobject> android_profile =
-      ProfileAndroid::FromProfile(profile)->GetJavaObject();
+  ScopedJavaLocalRef<jobject> android_profile = profile->GetJavaObject();
 
   SkBitmap image_bitmap = notification.image().AsBitmap();
 
diff --git a/chrome/browser/notifications/scheduler/display_agent_android.cc b/chrome/browser/notifications/scheduler/display_agent_android.cc
index c6f7ca0..ad012eb 100644
--- a/chrome/browser/notifications/scheduler/display_agent_android.cc
+++ b/chrome/browser/notifications/scheduler/display_agent_android.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/notifications/scheduler/notification_schedule_service_factory.h"
 #include "chrome/browser/notifications/scheduler/public/notification_schedule_service.h"
 #include "chrome/browser/notifications/scheduler/public/user_action_handler.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_key.h"
 #include "ui/gfx/android/java_bitmap.h"
 
diff --git a/chrome/browser/notifications/scheduler/notification_background_task_scheduler_android.cc b/chrome/browser/notifications/scheduler/notification_background_task_scheduler_android.cc
index 83cf8b2..3260d41 100644
--- a/chrome/browser/notifications/scheduler/notification_background_task_scheduler_android.cc
+++ b/chrome/browser/notifications/scheduler/notification_background_task_scheduler_android.cc
@@ -14,7 +14,6 @@
 #include "chrome/browser/notifications/scheduler/public/notification_background_task_scheduler.h"
 #include "chrome/browser/notifications/scheduler/public/notification_schedule_service.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/profiles/profile_key.h"
 
 // static
diff --git a/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc b/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc
index 9831309..add27aab 100644
--- a/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc
+++ b/chrome/browser/offline_pages/android/evaluation/offline_page_evaluation_bridge.cc
@@ -23,7 +23,6 @@
 #include "chrome/browser/offline_pages/offline_page_model_factory.h"
 #include "chrome/browser/offline_pages/request_coordinator_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/common/chrome_constants.h"
 #include "components/offline_pages/core/background/offliner.h"
 #include "components/offline_pages/core/background/offliner_policy.h"
diff --git a/chrome/browser/offline_pages/android/offline_page_bridge.cc b/chrome/browser/offline_pages/android/offline_page_bridge.cc
index 8e90738..a2ae035 100644
--- a/chrome/browser/offline_pages/android/offline_page_bridge.cc
+++ b/chrome/browser/offline_pages/android/offline_page_bridge.cc
@@ -31,7 +31,6 @@
 #include "chrome/browser/offline_pages/offline_page_utils.h"
 #include "chrome/browser/offline_pages/recent_tab_helper.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/profiles/profile_key.h"
 #include "chrome/browser/profiles/profile_key_android.h"
 #include "components/offline_pages/core/archive_validator.h"
diff --git a/chrome/browser/offline_pages/android/offline_test_util_jni.cc b/chrome/browser/offline_pages/android/offline_test_util_jni.cc
index fea88c9..b7feeaa5 100644
--- a/chrome/browser/offline_pages/android/offline_test_util_jni.cc
+++ b/chrome/browser/offline_pages/android/offline_test_util_jni.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/offline_pages/android/request_coordinator_bridge.h"
 #include "chrome/browser/offline_pages/offline_page_model_factory.h"
 #include "chrome/browser/offline_pages/request_coordinator_factory.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_key.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "components/offline_pages/core/background/request_coordinator.h"
diff --git a/chrome/browser/optimization_guide/model_execution/model_execution_validation_browsertest.cc b/chrome/browser/optimization_guide/model_execution/model_execution_validation_browsertest.cc
index 0359f2f..76ecee1 100644
--- a/chrome/browser/optimization_guide/model_execution/model_execution_validation_browsertest.cc
+++ b/chrome/browser/optimization_guide/model_execution/model_execution_validation_browsertest.cc
@@ -156,8 +156,8 @@
   }
 };
 
-// TODO(b/318433299, crbug.com/1520214): Flaky on linux-chromeos and win
-#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
+// TODO(b/318433299, crbug.com/1520214): Flaky on linux-chromeos, Win and Mac.
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
 #define MAYBE_ModelExecutionSuccess DISABLED_ModelExecutionSuccess
 #else
 #define MAYBE_ModelExecutionSuccess ModelExecutionSuccess
diff --git a/chrome/browser/password_manager/android/local_passwords_migration_warning_util.cc b/chrome/browser/password_manager/android/local_passwords_migration_warning_util.cc
index be43796..49238d3 100644
--- a/chrome/browser/password_manager/android/local_passwords_migration_warning_util.cc
+++ b/chrome/browser/password_manager/android/local_passwords_migration_warning_util.cc
@@ -9,7 +9,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
 #include "chrome/android/chrome_jni_headers/PasswordMigrationWarningBridge_jni.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "components/password_manager/core/browser/features/password_features.h"
 #include "components/password_manager/core/browser/password_store/split_stores_and_local_upm.h"
@@ -55,8 +55,7 @@
   SaveWarningShownTimestamp(profile->GetPrefs());
 
   Java_PasswordMigrationWarningBridge_showWarning(
-      AttachCurrentThread(), window->GetJavaObject(),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject(),
+      AttachCurrentThread(), window->GetJavaObject(), profile->GetJavaObject(),
       static_cast<int>(trigger_source));
 
   RecordPasswordMigrationWarningTriggerSource(trigger_source);
@@ -75,8 +74,7 @@
 
   Java_PasswordMigrationWarningBridge_showWarningWithActivity(
       AttachCurrentThread(), activity, bottom_sheet_controller,
-      ProfileAndroid::FromProfile(profile)->GetJavaObject(),
-      static_cast<int>(trigger_source));
+      profile->GetJavaObject(), static_cast<int>(trigger_source));
 
   RecordPasswordMigrationWarningTriggerSource(trigger_source);
 }
@@ -146,8 +144,7 @@
   }
 
   Java_PasswordMigrationWarningBridge_maybeShowPostMigrationSheet(
-      AttachCurrentThread(), window->GetJavaObject(),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject());
+      AttachCurrentThread(), window->GetJavaObject(), profile->GetJavaObject());
 }
 
 bool ShouldShowPostMigrationSheet(Profile* profile) {
diff --git a/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.cc b/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.cc
index d7ace93..cdbccc0 100644
--- a/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.cc
+++ b/chrome/browser/password_manager/android/password_checkup_launcher_helper_impl.cc
@@ -6,7 +6,6 @@
 
 #include "chrome/android/chrome_jni_headers/PasswordCheckupLauncher_jni.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 
 PasswordCheckupLauncherHelperImpl::~PasswordCheckupLauncherHelperImpl() =
     default;
@@ -29,8 +28,8 @@
     return;
   }
   Java_PasswordCheckupLauncher_launchCheckupOnDevice(
-      env, ProfileAndroid::FromProfile(profile)->GetJavaObject(),
-      windowAndroid->GetJavaObject(), static_cast<int>(passwordCheckReferrer),
+      env, profile->GetJavaObject(), windowAndroid->GetJavaObject(),
+      static_cast<int>(passwordCheckReferrer),
       account_email.empty()
           ? nullptr
           : base::android::ConvertUTF8ToJavaString(env, account_email));
diff --git a/chrome/browser/password_manager/android/password_manager_error_message_helper_bridge_impl.cc b/chrome/browser/password_manager/android/password_manager_error_message_helper_bridge_impl.cc
index 4548d26..9ff7e29 100644
--- a/chrome/browser/password_manager/android/password_manager_error_message_helper_bridge_impl.cc
+++ b/chrome/browser/password_manager/android/password_manager_error_message_helper_bridge_impl.cc
@@ -6,7 +6,6 @@
 
 #include "chrome/android/chrome_jni_headers/PasswordManagerErrorMessageHelperBridge_jni.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "ui/android/view_android.h"
 #include "ui/android/window_android.h"
 
@@ -24,7 +23,7 @@
 
   Java_PasswordManagerErrorMessageHelperBridge_startUpdateAccountCredentialsFlow(
       base::android::AttachCurrentThread(), window_android->GetJavaObject(),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject());
+      profile->GetJavaObject());
 }
 
 void PasswordManagerErrorMessageHelperBridgeImpl::
@@ -39,7 +38,7 @@
 
   Java_PasswordManagerErrorMessageHelperBridge_startTrustedVaultKeyRetrievalFlow(
       base::android::AttachCurrentThread(), window_android->GetJavaObject(),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject());
+      profile->GetJavaObject());
 }
 
 bool PasswordManagerErrorMessageHelperBridgeImpl::ShouldShowSignInErrorUI(
@@ -47,8 +46,7 @@
   Profile* profile =
       Profile::FromBrowserContext(web_contents->GetBrowserContext());
   return Java_PasswordManagerErrorMessageHelperBridge_shouldShowSignInErrorUI(
-      base::android::AttachCurrentThread(),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject());
+      base::android::AttachCurrentThread(), profile->GetJavaObject());
 }
 
 bool PasswordManagerErrorMessageHelperBridgeImpl::
@@ -56,8 +54,7 @@
   Profile* profile =
       Profile::FromBrowserContext(web_contents->GetBrowserContext());
   return Java_PasswordManagerErrorMessageHelperBridge_shouldShowUpdateGMSCoreErrorUI(
-      base::android::AttachCurrentThread(),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject());
+      base::android::AttachCurrentThread(), profile->GetJavaObject());
 }
 
 void PasswordManagerErrorMessageHelperBridgeImpl::SaveErrorUIShownTimestamp(
@@ -65,8 +62,7 @@
   Profile* profile =
       Profile::FromBrowserContext(web_contents->GetBrowserContext());
   Java_PasswordManagerErrorMessageHelperBridge_saveErrorUiShownTimestamp(
-      base::android::AttachCurrentThread(),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject());
+      base::android::AttachCurrentThread(), profile->GetJavaObject());
 }
 
 void PasswordManagerErrorMessageHelperBridgeImpl::LaunchGmsUpdate(
diff --git a/chrome/browser/password_manager/android/password_manager_launcher_android.cc b/chrome/browser/password_manager/android/password_manager_launcher_android.cc
index cb813305..962061b 100644
--- a/chrome/browser/password_manager/android/password_manager_launcher_android.cc
+++ b/chrome/browser/password_manager/android/password_manager_launcher_android.cc
@@ -7,7 +7,6 @@
 #include "base/android/jni_android.h"
 #include "chrome/android/chrome_jni_headers/PasswordManagerLauncher_jni.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "components/password_manager/core/browser/manage_passwords_referrer.h"
 #include "content/public/browser/web_contents.h"
 
@@ -33,8 +32,7 @@
     return g_manage_password_when_passkeys_present_override;
   }
   return Java_PasswordManagerLauncher_canManagePasswordsWhenPasskeysPresent(
-      base::android::AttachCurrentThread(),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject());
+      base::android::AttachCurrentThread(), profile->GetJavaObject());
 }
 
 void OverrideManagePasswordWhenPasskeysPresentForTesting(bool can_manage) {
diff --git a/chrome/browser/password_manager/android/save_update_password_message_delegate.h b/chrome/browser/password_manager/android/save_update_password_message_delegate.h
index bbf2ca8..5f4823a 100644
--- a/chrome/browser/password_manager/android/save_update_password_message_delegate.h
+++ b/chrome/browser/password_manager/android/save_update_password_message_delegate.h
@@ -13,7 +13,7 @@
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/password_edit_dialog/android/password_edit_dialog_bridge.h"
 #include "chrome/browser/password_manager/android/local_passwords_migration_warning_util.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/passwords/manage_passwords_state.h"
 #include "components/browser_ui/device_lock/android/device_lock_bridge.h"
 #include "components/messages/android/message_enums.h"
diff --git a/chrome/browser/platform_experience/win b/chrome/browser/platform_experience/win
index 3d27dce..203bb99 160000
--- a/chrome/browser/platform_experience/win
+++ b/chrome/browser/platform_experience/win
@@ -1 +1 @@
-Subproject commit 3d27dce9761364362fb066b34eca50b767ab53d4
+Subproject commit 203bb990e01a33360e543edbf03db386355a4d26
diff --git a/chrome/browser/policy/BUILD.gn b/chrome/browser/policy/BUILD.gn
index 58a3a2d1..2ecfc8e 100644
--- a/chrome/browser/policy/BUILD.gn
+++ b/chrome/browser/policy/BUILD.gn
@@ -244,7 +244,6 @@
     sources += [
       "test/autofill_policy_browsertest.cc",
       "test/autoplay_policy_browsertest.cc",
-      "test/block_truncated_cookies_policy_browsertest.cc",
       "test/bookmark_bar_enabled_browsertest.cc",
       "test/component_updater_policy_browsertest.cc",
       "test/developer_tools_policy_browsertest.cc",
diff --git a/chrome/browser/policy/cloud/cloud_policy_browsertest.cc b/chrome/browser/policy/cloud/cloud_policy_browsertest.cc
index 7767e3a..d7d5e1a 100644
--- a/chrome/browser/policy/cloud/cloud_policy_browsertest.cc
+++ b/chrome/browser/policy/cloud/cloud_policy_browsertest.cc
@@ -367,7 +367,13 @@
   std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
 };
 
-IN_PROC_BROWSER_TEST_F(CloudPolicyTest, FetchPolicy) {
+// TODO(crbug.com/40187980): The test fails on Windows.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_FetchPolicy DISABLED_FetchPolicy
+#else
+#define MAYBE_FetchPolicy FetchPolicy
+#endif
+IN_PROC_BROWSER_TEST_F(CloudPolicyTest, MAYBE_FetchPolicy) {
   PolicyService* policy_service = GetPolicyService();
   {
     base::RunLoop run_loop;
@@ -422,8 +428,9 @@
 }
 #endif
 
-// crbug.com/1230268 not working on Lacros.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/40187980, crbug.com/1230268): The test fails on Lacros and
+// Windows.
+#if BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_WIN)
 #define MAYBE_InvalidatePolicy DISABLED_InvalidatePolicy
 #else
 #define MAYBE_InvalidatePolicy InvalidatePolicy
diff --git a/chrome/browser/policy/cloud/cloud_policy_manager_browsertest.cc b/chrome/browser/policy/cloud/cloud_policy_manager_browsertest.cc
index e77b311..bec224cd 100644
--- a/chrome/browser/policy/cloud/cloud_policy_manager_browsertest.cc
+++ b/chrome/browser/policy/cloud/cloud_policy_manager_browsertest.cc
@@ -265,7 +265,13 @@
 #endif
 };
 
-IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, Register) {
+// TODO(crbug.com/40187980): The test fails on Windows.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_Register DISABLED_Register
+#else
+#define MAYBE_Register Register
+#endif
+IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, MAYBE_Register) {
   test_url_loader_factory_->SetInterceptor(
       base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
         // Accept one register request. The initial request should not include
@@ -285,7 +291,13 @@
   EXPECT_TRUE(policy_manager()->core()->client()->is_registered());
 }
 
-IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, RegisterFails) {
+// TODO(crbug.com/40187980): The test fails on Windows.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_RegisterFails DISABLED_RegisterFails
+#else
+#define MAYBE_RegisterFails RegisterFails
+#endif
+IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, MAYBE_RegisterFails) {
   test_url_loader_factory_->SetInterceptor(
       base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
         test_url_loader_factory_->AddResponse(request.url.spec(), std::string(),
@@ -317,7 +329,13 @@
   EXPECT_EQ(4, count);
 }
 
-IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, RegisterWithRetry) {
+// TODO(crbug.com/40187980): The test fails on Windows.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_RegisterWithRetry DISABLED_RegisterWithRetry
+#else
+#define MAYBE_RegisterWithRetry RegisterWithRetry
+#endif
+IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, MAYBE_RegisterWithRetry) {
   test_url_loader_factory_->SetInterceptor(
       base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
         em::DeviceRegisterRequest::Type expected_type =
diff --git a/chrome/browser/policy/cloud/component_cloud_policy_browsertest.cc b/chrome/browser/policy/cloud/component_cloud_policy_browsertest.cc
index 1e4fa51..b1efc7d7 100644
--- a/chrome/browser/policy/cloud/component_cloud_policy_browsertest.cc
+++ b/chrome/browser/policy/cloud/component_cloud_policy_browsertest.cc
@@ -306,8 +306,9 @@
   EXPECT_TRUE(policy_listener2.WaitUntilSatisfied());
 }
 
-// crbug.com/1230268 not working on Lacros.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
+// TODO(crbug.com/40187980, crbug.com/1230268): The test is flaky on Windows and
+// Lacros.
+#if BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_WIN)
 #define MAYBE_InstallNewExtension DISABLED_InstallNewExtension
 #else
 #define MAYBE_InstallNewExtension InstallNewExtension
@@ -346,7 +347,14 @@
 // get policy for components working again.
 // Signing out on Lacros is not possible.
 #if !BUILDFLAG(IS_CHROMEOS)
-IN_PROC_BROWSER_TEST_F(ComponentCloudPolicyTest, SignOutAndBackIn) {
+
+// TODO(crbug.com/40187980, crbug.com/1230268): The test is flaky on Windows.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_SignOutAndBackIn DISABLED_SignOutAndBackIn
+#else
+#define MAYBE_SignOutAndBackIn SignOutAndBackIn
+#endif
+IN_PROC_BROWSER_TEST_F(ComponentCloudPolicyTest, MAYBE_SignOutAndBackIn) {
   // Signout is not enabled when this feature is enabled.
   if (base::FeatureList::IsEnabled(kDisallowManagedProfileSignout)) {
     event_listener_->Reply("idle");
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index cc4517b6..a83d1b44 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -352,9 +352,6 @@
   { key::kAllowWebAuthnWithBrokenTlsCerts,
     webauthn::pref_names::kAllowWithBrokenCerts,
     base::Value::Type::BOOLEAN },
-  { key::kBlockTruncatedCookies,
-    prefs::kBlockTruncatedCookies,
-    base::Value::Type::BOOLEAN },
   { key::kHttpAllowlist,
     prefs::kHttpAllowlist,
     base::Value::Type::LIST },
diff --git a/chrome/browser/policy/policy_prefs_browsertest.cc b/chrome/browser/policy/policy_prefs_browsertest.cc
index a2bd8ab..20a7f80 100644
--- a/chrome/browser/policy/policy_prefs_browsertest.cc
+++ b/chrome/browser/policy/policy_prefs_browsertest.cc
@@ -79,7 +79,14 @@
 
 typedef PlatformBrowserTest PolicyPrefsTestCoverageTest;
 
-IN_PROC_BROWSER_TEST_F(PolicyPrefsTestCoverageTest, AllPoliciesHaveATestCase) {
+// TODO(crbug.com/341097718): Flaky on Mac.
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_AllPoliciesHaveATestCase DISABLED_AllPoliciesHaveATestCase
+#else
+#define MAYBE_AllPoliciesHaveATestCase AllPoliciesHaveATestCase
+#endif
+IN_PROC_BROWSER_TEST_F(PolicyPrefsTestCoverageTest,
+                       MAYBE_AllPoliciesHaveATestCase) {
   VerifyAllPoliciesHaveATestCase(GetTestCaseDir());
 }
 
diff --git a/chrome/browser/policy/test/block_truncated_cookies_policy_browsertest.cc b/chrome/browser/policy/test/block_truncated_cookies_policy_browsertest.cc
deleted file mode 100644
index 3232e364..0000000
--- a/chrome/browser/policy/test/block_truncated_cookies_policy_browsertest.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/test/scoped_feature_list.h"
-#include "base/values.h"
-#include "chrome/browser/policy/policy_test_utils.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "components/policy/core/common/policy_map.h"
-#include "components/policy/policy_constants.h"
-#include "content/public/browser/storage_partition.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/test/browser_test.h"
-#include "content/public/test/browser_test_utils.h"
-#include "net/base/features.h"
-#include "net/dns/mock_host_resolver.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
-
-namespace policy {
-
-class BlockTruncatedCookiesPolicyTest
-    : public PolicyTest,
-      public testing::WithParamInterface<bool> {
- public:
-  BlockTruncatedCookiesPolicyTest() {
-    // Ensure that the feature is always enabled so that the behavior is only
-    // controlled by the enterprise policy.
-    feature_list_.InitAndEnableFeature(net::features::kBlockTruncatedCookies);
-  }
-
-  void SetUpInProcessBrowserTestFixture() override {
-    PolicyTest::SetUpInProcessBrowserTestFixture();
-    UpdateBlockTruncatedCookiesPolicy(is_enabled());
-  }
-
-  void SetUpOnMainThread() override {
-    host_resolver()->AddRule("*", "127.0.0.1");
-  }
-
-  void UpdateBlockTruncatedCookiesPolicy(bool enable) {
-    PolicyMap policies;
-    policies.Set(key::kBlockTruncatedCookies, POLICY_LEVEL_RECOMMENDED,
-                 POLICY_SCOPE_USER, POLICY_SOURCE_COMMAND_LINE,
-                 base::Value(enable), nullptr);
-    provider_.UpdateChromePolicy(policies);
-  }
-
-  bool is_enabled() const { return GetParam(); }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_P(BlockTruncatedCookiesPolicyTest, RunTest) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), embedded_test_server()->GetURL("a.com", "/empty.html")));
-
-  content::WebContents* contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-
-  std::string cookie_js = "document.cookie = 'foo=ba\\x00r'; document.cookie;";
-
-  std::string cookie_string = EvalJs(contents, cookie_js).ExtractString();
-
-  if (is_enabled()) {
-    EXPECT_EQ("", cookie_string);
-  } else {
-    EXPECT_EQ("foo=ba", cookie_string);
-  }
-
-  // Now change the policy and verify that it takes affect. The change should
-  // cause the ProfileNetworkContextService pref change handler to fire, so
-  // use `RunUntilIdle()` to hopefully ensure that that happens. That will
-  // queue a message to Profile's NetworkContext's CookieManager that will
-  // update its CookieSettings. To ensure that actually gets sent we'll flush
-  // the corresponding message pipe. That CookieSettings should be shared by all
-  // the per-renderer RestrictedCookieManagers, but just in case there's some
-  // delay in that value propagating we will navigate to a new origin, which
-  // should create a new RestrictedCookieManager and should reliably use the
-  // updated setting. Using a new domain also means that we don't have to worry
-  // about deleting any cookies that were set above.
-  UpdateBlockTruncatedCookiesPolicy(!is_enabled());
-  base::RunLoop().RunUntilIdle();
-  browser()
-      ->profile()
-      ->GetDefaultStoragePartition()
-      ->FlushNetworkInterfaceForTesting();
-
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(
-      browser(), embedded_test_server()->GetURL("b.com", "/empty.html")));
-
-  contents = browser()->tab_strip_model()->GetActiveWebContents();
-
-  cookie_string = EvalJs(contents, cookie_js).ExtractString();
-
-  // Check for the opposite behavior from above.
-  if (is_enabled()) {
-    EXPECT_EQ("foo=ba", cookie_string);
-  } else {
-    EXPECT_EQ("", cookie_string);
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(BlockTruncatedCookiesPolicyTestP,
-                         BlockTruncatedCookiesPolicyTest,
-                         testing::Values(true, false));
-}  // namespace policy
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index e7e44318..7c0b8d4 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1088,6 +1088,9 @@
     "settings.a11y.mouse_keys.disable_in_text_fields";
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+// Deprecated 05/2024.
+constexpr char kBlockTruncatedCookies[] = "profile.cookie_block_truncated";
+
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
@@ -1561,6 +1564,9 @@
   registry->RegisterBooleanPref(kAccessibilityMouseKeysDisableInTextFields,
                                 true);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+  // Deprecated 05/2024.
+  registry->RegisterBooleanPref(kBlockTruncatedCookies, true);
 }
 
 void ClearSyncRequestedPrefAndMaybeMigrate(PrefService* profile_prefs) {
@@ -2287,7 +2293,6 @@
   registry->RegisterBooleanPref(kClearUserDataDir1Pref, false);
 #endif
 
-  registry->RegisterBooleanPref(prefs::kBlockTruncatedCookies, true);
   registry->RegisterBooleanPref(
       prefs::kManagedPrivateNetworkAccessRestrictionsEnabled, false);
 
@@ -2920,6 +2925,9 @@
   profile_prefs->ClearPref(kAccessibilityMouseKeysDisableInTextFields);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+  // Added 05/2024.
+  profile_prefs->ClearPref(kBlockTruncatedCookies);
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 
diff --git a/chrome/browser/privacy_sandbox/android/privacy_sandbox_bridge.cc b/chrome/browser/privacy_sandbox/android/privacy_sandbox_bridge.cc
index 8a1dafe3..2c01275 100644
--- a/chrome/browser/privacy_sandbox/android/privacy_sandbox_bridge.cc
+++ b/chrome/browser/privacy_sandbox/android/privacy_sandbox_bridge.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "components/privacy_sandbox/canonical_topic.h"
 #include "components/privacy_sandbox/privacy_sandbox_settings.h"
 #include "components/strings/grit/components_strings.h"
@@ -35,7 +34,7 @@
 PrivacySandboxService* GetPrivacySandboxService(
     const base::android::JavaRef<jobject>& j_profile) {
   return PrivacySandboxServiceFactory::GetForProfile(
-      ProfileAndroid::FromProfileAndroid(j_profile));
+      Profile::FromJavaObject(j_profile));
 }
 
 std::vector<jni_zero::ScopedJavaLocalRef<jobject>> ToJavaTopicsArray(
@@ -227,6 +226,6 @@
     JNIEnv* env,
     const JavaParamRef<jobject>& j_profile) {
   PrivacySandboxSettingsFactory::GetForProfile(
-      ProfileAndroid::FromProfileAndroid(j_profile))
+      Profile::FromJavaObject(j_profile))
       ->SetAllPrivacySandboxAllowedForTesting();  // IN-TEST
 }
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.cc
index b9699dc8c..a2ef1dd1 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.cc
@@ -11,6 +11,7 @@
 #include "chrome/browser/first_party_sets/first_party_sets_policy_service_factory.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h"
+#include "chrome/browser/privacy_sandbox/tracking_protection_settings_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/tpcd/experiment/eligibility_service_factory.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
@@ -72,6 +73,7 @@
   DependsOn(CookieSettingsFactory::GetInstance());
   DependsOn(HostContentSettingsMapFactory::GetInstance());
   DependsOn(browsing_topics::BrowsingTopicsServiceFactory::GetInstance());
+  DependsOn(TrackingProtectionSettingsFactory::GetInstance());
 #if !BUILDFLAG(IS_ANDROID)
   DependsOn(TrustSafetySentimentServiceFactory::GetInstance());
 #endif
@@ -90,6 +92,7 @@
   Profile* profile = Profile::FromBrowserContext(context);
   return std::make_unique<PrivacySandboxServiceImpl>(
       PrivacySandboxSettingsFactory::GetForProfile(profile),
+      TrackingProtectionSettingsFactory::GetForProfile(profile),
       CookieSettingsFactory::GetForProfile(profile), profile->GetPrefs(),
       profile->GetDefaultStoragePartition()->GetInterestGroupManager(),
       GetProfileType(profile),
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.cc
index 8799d2bd..31bae548 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.cc
@@ -70,7 +70,8 @@
 
 // Returns whether 3P cookies are blocked by |cookie_settings|. This can be
 // either through blocking 3P cookies directly, or blocking all cookies.
-bool AreThirdPartyCookiesBlocked(
+// Blocking in this case also covers the "3P cookies limited" state.
+bool ShouldBlockThirdPartyOrFirstPartyCookies(
     content_settings::CookieSettings* cookie_settings) {
   const auto default_content_setting =
       cookie_settings->GetDefaultCookieSetting();
@@ -78,6 +79,25 @@
          default_content_setting == ContentSetting::CONTENT_SETTING_BLOCK;
 }
 
+// Similar to the function above, but checks for ALL 3P cookies to be blocked
+// pre and post 3PCD.
+bool AreAllThirdPartyCookiesBlocked(
+    content_settings::CookieSettings* cookie_settings,
+    PrefService* prefs,
+    privacy_sandbox::TrackingProtectionSettings* tracking_protection_settings) {
+  // Check if 1PCs are blocked.
+  if (cookie_settings->GetDefaultCookieSetting() ==
+      ContentSetting::CONTENT_SETTING_BLOCK) {
+    return true;
+  }
+  // Check if all 3PCs are blocked.
+  return tracking_protection_settings->AreAllThirdPartyCookiesBlocked() ||
+         (!tracking_protection_settings->IsTrackingProtection3pcdEnabled() &&
+          prefs->GetInteger(prefs::kCookieControlsMode) ==
+              static_cast<int>(
+                  content_settings::CookieControlsMode::kBlockThirdParty));
+}
+
 // Sorts |topics| alphabetically by topic display name for display.
 // In addition, removes duplicate topics.
 void SortAndDeduplicateTopicsForDisplay(
@@ -240,6 +260,7 @@
 
 PrivacySandboxServiceImpl::PrivacySandboxServiceImpl(
     privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
+    privacy_sandbox::TrackingProtectionSettings* tracking_protection_settings,
     scoped_refptr<content_settings::CookieSettings> cookie_settings,
     PrefService* pref_service,
     content::InterestGroupManager* interest_group_manager,
@@ -252,6 +273,7 @@
     browsing_topics::BrowsingTopicsService* browsing_topics_service,
     first_party_sets::FirstPartySetsPolicyService* first_party_sets_service)
     : privacy_sandbox_settings_(privacy_sandbox_settings),
+      tracking_protection_settings_(tracking_protection_settings),
       cookie_settings_(cookie_settings),
       pref_service_(pref_service),
       interest_group_manager_(interest_group_manager),
@@ -266,6 +288,7 @@
   DCHECK(privacy_sandbox_settings_);
   DCHECK(pref_service_);
   DCHECK(cookie_settings_);
+  CHECK(tracking_protection_settings_);
 
   // Register observers for the Privacy Sandbox preferences.
   user_prefs_registrar_.Init(pref_service_);
@@ -326,8 +349,15 @@
 
 PrivacySandboxService::PromptType
 PrivacySandboxServiceImpl::GetRequiredPromptType() {
-  const auto third_party_cookies_blocked =
-      AreThirdPartyCookiesBlocked(cookie_settings_.get());
+  bool third_party_cookies_blocked;
+  if (base::FeatureList::IsEnabled(
+          privacy_sandbox::kPrivacySandboxAdsDialogDisabledOnAll3PCBlock)) {
+    third_party_cookies_blocked = AreAllThirdPartyCookiesBlocked(
+        cookie_settings_.get(), pref_service_, tracking_protection_settings_);
+  } else {
+    third_party_cookies_blocked =
+        ShouldBlockThirdPartyOrFirstPartyCookies(cookie_settings_.get());
+  }
   return GetRequiredPromptTypeInternal(
       pref_service_, profile_type_, privacy_sandbox_settings_,
       third_party_cookies_blocked,
@@ -1160,7 +1190,7 @@
   // side of privacy, this init logic is run per-device (the pref recording that
   // init has been run is not synced). If any of the user's devices local state
   // would disable the pref, it is disabled across all devices.
-  if (AreThirdPartyCookiesBlocked(cookie_settings_.get())) {
+  if (ShouldBlockThirdPartyOrFirstPartyCookies(cookie_settings_.get())) {
     pref_service_->SetBoolean(prefs::kPrivacySandboxRelatedWebsiteSetsEnabled,
                               false);
   }
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.h b/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.h
index c876b2f0..c40364b 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.h
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl.h
@@ -5,7 +5,9 @@
 #ifndef CHROME_BROWSER_PRIVACY_SANDBOX_PRIVACY_SANDBOX_SERVICE_IMPL_H_
 #define CHROME_BROWSER_PRIVACY_SANDBOX_PRIVACY_SANDBOX_SERVICE_IMPL_H_
 
+// clang-format off
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service.h"
+// clang-format on
 
 #include <set>
 
@@ -17,6 +19,7 @@
 #include "components/privacy_sandbox/canonical_topic.h"
 #include "components/privacy_sandbox/privacy_sandbox_prefs.h"
 #include "components/privacy_sandbox/privacy_sandbox_settings.h"
+#include "components/privacy_sandbox/tracking_protection_settings.h"
 #include "components/profile_metrics/browser_profile_type.h"
 #include "content/public/browser/interest_group_manager.h"
 #include "net/base/schemeful_site.h"
@@ -47,6 +50,7 @@
  public:
   PrivacySandboxServiceImpl(
       privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
+      privacy_sandbox::TrackingProtectionSettings* tracking_protection_settings,
       scoped_refptr<content_settings::CookieSettings> cookie_settings,
       PrefService* pref_service,
       content::InterestGroupManager* interest_group_manager,
@@ -337,6 +341,8 @@
  private:
   raw_ptr<privacy_sandbox::PrivacySandboxSettings, DanglingUntriaged>
       privacy_sandbox_settings_;
+  raw_ptr<privacy_sandbox::TrackingProtectionSettings>
+      tracking_protection_settings_;
   scoped_refptr<content_settings::CookieSettings> cookie_settings_;
   raw_ptr<PrefService> pref_service_;
   raw_ptr<content::InterestGroupManager> interest_group_manager_;
diff --git a/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl_unittest.cc b/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl_unittest.cc
index f92458d..d753816 100644
--- a/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl_unittest.cc
+++ b/chrome/browser/privacy_sandbox/privacy_sandbox_service_impl_unittest.cc
@@ -347,9 +347,9 @@
             profile());
 #endif
     privacy_sandbox_service_ = std::make_unique<PrivacySandboxServiceImpl>(
-        privacy_sandbox_settings(), cookie_settings(), profile()->GetPrefs(),
-        test_interest_group_manager(), GetProfileType(),
-        browsing_data_remover(), host_content_settings_map(),
+        privacy_sandbox_settings(), tracking_protection_settings(),
+        cookie_settings(), profile()->GetPrefs(), test_interest_group_manager(),
+        GetProfileType(), browsing_data_remover(), host_content_settings_map(),
 #if !BUILDFLAG(IS_ANDROID)
         mock_sentiment_service(),
 #endif
@@ -969,6 +969,24 @@
     EXPECT_THAT(service->GetBlockedTopics(), ElementsAre(topic3, topic4));
   }
 }
+using PrivacySandboxServiceDeathTest = PrivacySandboxServiceTest;
+
+TEST_F(PrivacySandboxServiceDeathTest, TPSettingsNullExpectDeath) {
+  ASSERT_DEATH(
+      {
+        PrivacySandboxServiceImpl(
+            privacy_sandbox_settings(),
+            /*tracking_protection_settings=*/nullptr, cookie_settings(),
+            profile()->GetPrefs(), test_interest_group_manager(),
+            GetProfileType(), browsing_data_remover(),
+            host_content_settings_map(),
+#if !BUILDFLAG(IS_ANDROID)
+            mock_sentiment_service(),
+#endif
+            mock_browsing_topics_service(), first_party_sets_policy_service());
+      },
+      "");
+}
 
 TEST_F(PrivacySandboxServiceTest,
        FirstPartySetsNotRelevantMetricAllowedCookies) {
@@ -2054,14 +2072,30 @@
 }
 #endif
 
-TEST_F(PrivacySandboxServiceM1PromptTest, ThirdPartyCookiesBlocked) {
+TEST_F(PrivacySandboxServiceM1PromptTest, ThirdPartyCookiesBlockedPostTP3PC) {
+  // If third party cookies are blocked, set the suppressed reason as
+  // kThirdPartyCookiesBlocked and return kNone.
+  RunTestCase(
+      TestState{{kM1PromptPreviouslySuppressedReason,
+                 static_cast<int>(PromptSuppressedReason::kNone)},
+                {kBlockAll3pcToggleEnabledUserPrefValue, true},
+                {kTrackingProtection3pcdEnabledUserPrefValue, true}},
+      TestInput{{kForceChromeBuild, true}},
+      TestOutput{{kPromptType, static_cast<int>(PromptType::kNone)},
+                 {kM1PromptSuppressedReason,
+                  static_cast<int>(
+                      PromptSuppressedReason::kThirdPartyCookiesBlocked)}});
+}
+
+TEST_F(PrivacySandboxServiceM1PromptTest, ThirdPartyCookiesBlockedPreTP3PC) {
   // If third party cookies are blocked, set the suppressed reason as
   // kThirdPartyCookiesBlocked and return kNone.
   RunTestCase(
       TestState{{kM1PromptPreviouslySuppressedReason,
                  static_cast<int>(PromptSuppressedReason::kNone)},
                 {kCookieControlsModeUserPrefValue,
-                 content_settings::CookieControlsMode::kBlockThirdParty}},
+                 content_settings::CookieControlsMode::kBlockThirdParty},
+                {kTrackingProtection3pcdEnabledUserPrefValue, false}},
       TestInput{{kForceChromeBuild, true}},
       TestOutput{{kPromptType, static_cast<int>(PromptType::kNone)},
                  {kM1PromptSuppressedReason,
diff --git a/chrome/browser/profiles/BUILD.gn b/chrome/browser/profiles/BUILD.gn
index 0b5e4239..37b29bf0 100644
--- a/chrome/browser/profiles/BUILD.gn
+++ b/chrome/browser/profiles/BUILD.gn
@@ -19,7 +19,6 @@
   if (is_android) {
     sources += [
       "profile_android.cc",
-      "profile_android.h",
       "profile_key_android.cc",
       "profile_key_android.h",
     ]
diff --git a/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/Profile.java b/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/Profile.java
index d8684b0a..a02b1407 100644
--- a/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/Profile.java
+++ b/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/Profile.java
@@ -17,14 +17,14 @@
 
 /** Wrapper that allows passing a Profile reference around in the Java layer. */
 public class Profile implements BrowserContextHandle {
-    /** Pointer to the Native-side ProfileAndroid. */
-    private long mNativeProfileAndroid;
+    /** Pointer to the Native-side Profile. */
+    private long mNativeProfile;
 
     private final @Nullable OTRProfileID mOtrProfileId;
 
     @CalledByNative
-    private Profile(long nativeProfileAndroid, @Nullable OTRProfileID otrProfileId) {
-        mNativeProfileAndroid = nativeProfileAndroid;
+    private Profile(long nativeProfile, @Nullable OTRProfileID otrProfileId) {
+        mNativeProfile = nativeProfile;
         mOtrProfileId = otrProfileId;
     }
 
@@ -61,7 +61,7 @@
     }
 
     public Profile getOriginalProfile() {
-        return ProfileJni.get().getOriginalProfile(mNativeProfileAndroid);
+        return ProfileJni.get().getOriginalProfile(mNativeProfile);
     }
 
     /**
@@ -73,8 +73,7 @@
      */
     public Profile getOffTheRecordProfile(OTRProfileID profileID, boolean createIfNeeded) {
         assert profileID != null;
-        return ProfileJni.get()
-                .getOffTheRecordProfile(mNativeProfileAndroid, profileID, createIfNeeded);
+        return ProfileJni.get().getOffTheRecordProfile(mNativeProfile, profileID, createIfNeeded);
     }
 
     /**
@@ -84,7 +83,7 @@
      * @param createIfNeeded Boolean indicating the profile should be created if doesn't exist.
      */
     public Profile getPrimaryOTRProfile(boolean createIfNeeded) {
-        return ProfileJni.get().getPrimaryOTRProfile(mNativeProfileAndroid, createIfNeeded);
+        return ProfileJni.get().getPrimaryOTRProfile(mNativeProfile, createIfNeeded);
     }
 
     /**
@@ -101,12 +100,12 @@
      */
     public boolean hasOffTheRecordProfile(OTRProfileID profileID) {
         assert profileID != null;
-        return ProfileJni.get().hasOffTheRecordProfile(mNativeProfileAndroid, profileID);
+        return ProfileJni.get().hasOffTheRecordProfile(mNativeProfile, profileID);
     }
 
     /** Returns if primary OffTheRecord profile exists. */
     public boolean hasPrimaryOTRProfile() {
-        return ProfileJni.get().hasPrimaryOTRProfile(mNativeProfileAndroid);
+        return ProfileJni.get().hasPrimaryOTRProfile(mNativeProfile);
     }
 
     /** Returns if the profile is a primary OTR Profile. */
@@ -115,7 +114,7 @@
     }
 
     public ProfileKey getProfileKey() {
-        return ProfileJni.get().getProfileKey(mNativeProfileAndroid);
+        return ProfileJni.get().getProfileKey(mNativeProfile);
     }
 
     public boolean isOffTheRecord() {
@@ -130,12 +129,12 @@
      */
     @Deprecated
     public boolean isChild() {
-        return ProfileJni.get().isChild(mNativeProfileAndroid);
+        return ProfileJni.get().isChild(mNativeProfile);
     }
 
     /** Wipes all data for this profile. */
     public void wipe() {
-        ProfileJni.get().wipe(mNativeProfileAndroid);
+        ProfileJni.get().wipe(mNativeProfile);
     }
 
     /**
@@ -143,7 +142,7 @@
      */
     @VisibleForTesting
     public boolean isNativeInitialized() {
-        return mNativeProfileAndroid != 0;
+        return mNativeProfile != 0;
     }
 
     /**
@@ -151,19 +150,19 @@
      * get a more debuggable stacktrace than failing on native-side when dereferencing.
      */
     public void ensureNativeInitialized() {
-        if (mNativeProfileAndroid == 0) {
+        if (mNativeProfile == 0) {
             throw new RuntimeException("Native profile pointer not initialized.");
         }
     }
 
     @Override
     public long getNativeBrowserContextPointer() {
-        return mNativeProfileAndroid;
+        return mNativeProfile;
     }
 
     @CalledByNative
     private void onNativeDestroyed() {
-        mNativeProfileAndroid = 0;
+        mNativeProfile = 0;
 
         if (isPrimaryOTRProfile()) {
             CookiesFetcher.scheduleDeleteCookies();
@@ -174,7 +173,7 @@
 
     @CalledByNative
     private long getNativePointer() {
-        return mNativeProfileAndroid;
+        return mNativeProfile;
     }
 
     @NativeMethods
diff --git a/chrome/browser/profiles/android/profile_resolver.cc b/chrome/browser/profiles/android/profile_resolver.cc
index 7f077f9..f1e3d8c 100644
--- a/chrome/browser/profiles/android/profile_resolver.cc
+++ b/chrome/browser/profiles/android/profile_resolver.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/android/proto/profile_token.pb.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/profiles/profile_key.h"
 #include "chrome/browser/profiles/profile_key_android.h"
 #include "chrome/browser/profiles/profile_manager.h"
@@ -85,8 +84,7 @@
 void OnResolvedProfile(const JavaRef<jobject>& j_callback, Profile* profile) {
   ScopedJavaLocalRef<jobject> j_profile;
   if (profile) {
-    ProfileAndroid* profile_android = ProfileAndroid::FromProfile(profile);
-    j_profile = profile_android->GetJavaObject();
+    j_profile = profile->GetJavaObject();
   }
   base::android::RunObjectCallbackAndroid(j_callback, j_profile);
 }
diff --git a/chrome/browser/profiles/profile.h b/chrome/browser/profiles/profile.h
index 71bc4e6..d31f74e 100644
--- a/chrome/browser/profiles/profile.h
+++ b/chrome/browser/profiles/profile.h
@@ -516,9 +516,7 @@
   }
 
 #if BUILDFLAG(IS_ANDROID)
-  // TODO(agrieve): Delete this no-op.
-  static Profile* FromProfile(Profile* profile) { return profile; }
-  static Profile* FromProfileAndroid(const jni_zero::JavaRef<jobject>& obj);
+  static Profile* FromJavaObject(const jni_zero::JavaRef<jobject>& obj);
   jni_zero::ScopedJavaLocalRef<jobject> GetJavaObject() const;
 #endif  // BUILDFLAG(IS_ANDROID)
  protected:
diff --git a/chrome/browser/profiles/profile_android.cc b/chrome/browser/profiles/profile_android.cc
index 0e50294..c014303 100644
--- a/chrome/browser/profiles/profile_android.cc
+++ b/chrome/browser/profiles/profile_android.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
@@ -23,7 +23,7 @@
 
 template <>
 Profile* FromJniType<Profile*>(JNIEnv* env, const JavaRef<jobject>& j_profile) {
-  return Profile::FromProfileAndroid(j_profile);
+  return Profile::FromJavaObject(j_profile);
 }
 
 template <>
@@ -38,7 +38,7 @@
 }  // namespace jni_zero
 
 // static
-Profile* Profile::FromProfileAndroid(const JavaRef<jobject>& obj) {
+Profile* Profile::FromJavaObject(const JavaRef<jobject>& obj) {
   if (!obj) {
     return nullptr;
   }
diff --git a/chrome/browser/profiles/profile_android.h b/chrome/browser/profiles/profile_android.h
deleted file mode 100644
index b2cbc2f..0000000
--- a/chrome/browser/profiles/profile_android.h
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2013 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_PROFILES_PROFILE_ANDROID_H_
-#define CHROME_BROWSER_PROFILES_PROFILE_ANDROID_H_
-
-#include "chrome/browser/profiles/profile.h"
-
-using ProfileAndroid = Profile;
-
-#endif  // CHROME_BROWSER_PROFILES_PROFILE_ANDROID_H_
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 3dc734a..0a64906 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -97,6 +97,7 @@
 #include "chrome/browser/ui/exclusive_access/keyboard_lock_controller.h"
 #include "chrome/browser/ui/lens/lens_overlay_controller.h"
 #include "chrome/browser/ui/lens/lens_overlay_image_helper.h"
+#include "chrome/browser/ui/lens/lens_overlay_invocation_source.h"
 #include "chrome/browser/ui/passwords/ui_utils.h"
 #include "chrome/browser/ui/qrcode_generator/qrcode_generator_bubble_controller.h"
 #include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble.h"
@@ -4279,7 +4280,7 @@
       LensOverlayController::GetController(source_web_contents_);
   CHECK(controller);
   controller->ShowUIWithPendingRegion(
-      LensOverlayController::InvocationSource::kContentAreaContextMenuPage,
+      lens::LensOverlayInvocationSource::kContentAreaContextMenuImage,
       lens::GetCenterRotatedBoxFromViewAndImageBounds(view_bounds,
                                                       image_bounds));
 }
@@ -4300,7 +4301,7 @@
         LensOverlayController::GetController(source_web_contents_);
     CHECK(controller);
     controller->ShowUI(
-        LensOverlayController::InvocationSource::kContentAreaContextMenuPage);
+        lens::LensOverlayInvocationSource::kContentAreaContextMenuPage);
     UserEducationService::MaybeNotifyPromoFeatureUsed(
         GetBrowserContext(), lens::features::kLensOverlay);
     return;
diff --git a/chrome/browser/resources/ash/settings/os_people_page/os_sync_browser_proxy.ts b/chrome/browser/resources/ash/settings/os_people_page/os_sync_browser_proxy.ts
index 20ff4f9..56ac877d 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/os_sync_browser_proxy.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/os_sync_browser_proxy.ts
@@ -69,6 +69,6 @@
   }
 
   setOsSyncDatatypes(osSyncPrefs: OsSyncPrefs): void {
-    return chrome.send('SetOsSyncDatatypes', [osSyncPrefs]);
+    chrome.send('SetOsSyncDatatypes', [osSyncPrefs]);
   }
 }
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index 6f122e8..10d0b2e 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -81,6 +81,7 @@
   "common/key_sequence.ts",
   "common/key_util.ts",
   "common/msgs.ts",
+  "common/spannable.ts",
   "common/tts_types.ts",
   "learn_mode/learn_mode.ts",
   "log_page/log.ts",
@@ -168,7 +169,6 @@
   "common/panel_menu_data.js",
   "common/permission_checker.js",
   "common/role_type.js",
-  "common/spannable.js",
   "common/settings_manager.js",
   "common/tree_dumper.js",
 ]
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable.js
deleted file mode 100644
index b627837..0000000
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable.js
+++ /dev/null
@@ -1,508 +0,0 @@
-// Copyright 2014 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Class which allows construction of annotated strings.
- */
-import {TestImportManager} from '/common/testing/test_import_manager.js';
-
-export class Spannable {
-  /**
-   * @param {string|!Spannable=} opt_string Initial value of the spannable.
-   * @param {*=} opt_annotation Initial annotation for the entire string.
-   */
-  constructor(opt_string, opt_annotation) {
-    /**
-     * Underlying string.
-     * @type {string}
-     * @private
-     */
-    this.string_ = opt_string instanceof Spannable ? '' : opt_string || '';
-
-    /**
-     * Spans (annotations).
-     * @type {!Array<!SpanStruct>}
-     * @private
-     */
-    this.spans_ = [];
-
-    // Append the initial spannable.
-    if (opt_string instanceof Spannable) {
-      this.append(opt_string);
-    }
-
-    // Optionally annotate the entire string.
-    if (goog.isDef(opt_annotation)) {
-      const len = this.string_.length;
-      this.spans_.push({value: opt_annotation, start: 0, end: len});
-    }
-  }
-
-  /** @override */
-  toString() {
-    return this.string_;
-  }
-
-  /** @return {number} The length of the string */
-  get length() {
-    return this.string_.length;
-  }
-
-  /**
-   * Adds a span to some region of the string.
-   * @param {*} value Annotation.
-   * @param {number} start Starting index (inclusive).
-   * @param {number} end Ending index (exclusive).
-   */
-  setSpan(value, start, end) {
-    this.removeSpan(value);
-    this.setSpanInternal(value, start, end);
-  }
-
-  /**
-   * @param {*} value Annotation.
-   * @param {number} start Starting index (inclusive).
-   * @param {number} end Ending index (exclusive).
-   * @protected
-   */
-  setSpanInternal(value, start, end) {
-    if (0 <= start && start <= end && end <= this.string_.length) {
-      // Zero-length spans are explicitly allowed, because it is possible to
-      // query for position by annotation as well as the reverse.
-      this.spans_.push({value, start, end});
-      this.spans_.sort(function(a, b) {
-        let ret = a.start - b.start;
-        if (ret === 0) {
-          ret = a.end - b.end;
-        }
-        return ret;
-      });
-    } else {
-      throw new RangeError(
-          'span out of range (start=' + start + ', end=' + end +
-          ', len=' + this.string_.length + ')');
-    }
-  }
-
-  /**
-   * Removes a span.
-   * @param {*} value Annotation.
-   */
-  removeSpan(value) {
-    for (let i = this.spans_.length - 1; i >= 0; i--) {
-      if (this.spans_[i].value === value) {
-        this.spans_.splice(i, 1);
-      }
-    }
-  }
-
-  /**
-   * Appends another Spannable or string to this one.
-   * @param {string|!Spannable} other String or spannable to concatenate.
-   */
-  append(other) {
-    if (other instanceof Spannable) {
-      const otherSpannable = /** @type {!Spannable} */ (other);
-      const originalLength = this.length;
-      this.string_ += otherSpannable.string_;
-      other.spans_.forEach(
-          span => this.setSpan(
-              span.value, span.start + originalLength,
-              span.end + originalLength));
-    } else if (typeof other === 'string') {
-      this.string_ += /** @type {string} */ (other);
-    }
-  }
-
-  /**
-   * Returns the first value matching a position.
-   * @param {number} position Position to query.
-   * @return {*} Value annotating that position, or undefined if none is
-   *     found.
-   */
-  getSpan(position) {
-    return valueOfSpan(this.spans_.find(spanCoversPosition(position)));
-  }
-
-  /**
-   * Returns the first span value which is an instance of a given constructor.
-   * @param {!Function} constructor Constructor.
-   * @return {*} Object if found; undefined otherwise.
-   */
-  getSpanInstanceOf(constructor) {
-    return valueOfSpan(this.spans_.find(spanInstanceOf(constructor)));
-  }
-
-  /**
-   * Returns all span values which are an instance of a given constructor.
-   * Spans are returned in the order of their starting index and ending index
-   * for spans with equals tarting indices.
-   * @param {!Function} constructor Constructor.
-   * @return {!Array<Object>} Array of object.
-   */
-  getSpansInstanceOf(constructor) {
-    return (this.spans_.filter(spanInstanceOf(constructor)).map(valueOfSpan));
-  }
-
-  /**
-   * Returns all spans matching a position.
-   * @param {number} position Position to query.
-   * @return {!Array} Values annotating that position.
-   */
-  getSpans(position) {
-    return (this.spans_.filter(spanCoversPosition(position)).map(valueOfSpan));
-  }
-
-  /**
-   * Returns whether a span is contained in this object.
-   * @param {*} value Annotation.
-   * @return {boolean}
-   */
-  hasSpan(value) {
-    return this.spans_.some(spanValueIs(value));
-  }
-
-  /**
-   * Returns the start of the requested span. Throws if the span doesn't exist
-   * in this object.
-   * @param {*} value Annotation.
-   * @return {number}
-   */
-  getSpanStart(value) {
-    return this.getSpanByValueOrThrow_(value).start;
-  }
-
-  /**
-   * Returns the end of the requested span. Throws if the span doesn't exist
-   * in this object.
-   * @param {*} value Annotation.
-   * @return {number}
-   */
-  getSpanEnd(value) {
-    return this.getSpanByValueOrThrow_(value).end;
-  }
-
-  /**
-   * @param {*} value Annotation.
-   * @return {!Array<{start: number, end: number}>}
-   */
-  getSpanIntervals(value) {
-    return this.spans_.filter(span => span.value === value).map(span => {
-      return {start: span.start, end: span.end};
-    });
-  }
-
-  /**
-   * Returns the number of characters covered by the given span. Throws if
-   * the span is not in this object.
-   * @param {*} value
-   * @return {number}
-   */
-  getSpanLength(value) {
-    const span = this.getSpanByValueOrThrow_(value);
-    return span.end - span.start;
-  }
-
-  /**
-   * Gets the internal object for a span or throws if the span doesn't exist.
-   * @param {*} value The annotation.
-   * @return {!SpanStruct}
-   * @private
-   */
-  getSpanByValueOrThrow_(value) {
-    const span = this.spans_.find(spanValueIs(value));
-    if (span) {
-      return span;
-    }
-    throw new Error('Span ' + value + ' doesn\'t exist in spannable');
-  }
-
-  /**
-   * Returns a substring of this spannable.
-   * Note that while similar to String#substring, this function is much less
-   * permissive about its arguments. It does not accept arguments in the wrong
-   * order or out of bounds.
-   *
-   * @param {number} start Start index, inclusive.
-   * @param {number=} opt_end End index, exclusive.
-   *     If excluded, the length of the string is used instead.
-   * @return {!Spannable} Substring requested.
-   */
-  substring(start, opt_end) {
-    const end = goog.isDef(opt_end) ? opt_end : this.string_.length;
-
-    if (start < 0 || end > this.string_.length || start > end) {
-      throw new RangeError('substring indices out of range');
-    }
-
-    const result = new Spannable(this.string_.substring(start, end));
-    this.spans_.forEach(span => {
-      if (span.start <= end && span.end >= start) {
-        const newStart = Math.max(0, span.start - start);
-        const newEnd = Math.min(end - start, span.end - start);
-        result.spans_.push({value: span.value, start: newStart, end: newEnd});
-      }
-    });
-    return result;
-  }
-
-  /**
-   * Trims whitespace from the beginning.
-   * @return {!Spannable} String with whitespace removed.
-   */
-  trimLeft() {
-    return this.trim_(true, false);
-  }
-
-  /**
-   * Trims whitespace from the end.
-   * @return {!Spannable} String with whitespace removed.
-   */
-  trimRight() {
-    return this.trim_(false, true);
-  }
-
-  /**
-   * Trims whitespace from the beginning and end.
-   * @return {!Spannable} String with whitespace removed.
-   */
-  trim() {
-    return this.trim_(true, true);
-  }
-
-  /**
-   * Trims whitespace from either the beginning and end or both.
-   * @param {boolean} trimStart Trims whitespace from the start of a string.
-   * @param {boolean} trimEnd Trims whitespace from the end of a string.
-   * @return {!Spannable} String with whitespace removed.
-   * @private
-   */
-  trim_(trimStart, trimEnd) {
-    if (!trimStart && !trimEnd) {
-      return this;
-    }
-
-    // Special-case whitespace-only strings, including the empty string.
-    // As an arbitrary decision, we treat this as trimming the whitespace off
-    // the end, rather than the beginning, of the string.
-    // This choice affects which spans are kept.
-    if (/^\s*$/.test(this.string_)) {
-      return this.substring(0, 0);
-    }
-
-    // Otherwise, we have at least one non-whitespace character to use as an
-    // anchor when trimming.
-    const trimmedStart = trimStart ? this.string_.match(/^\s*/)[0].length : 0;
-    const trimmedEnd =
-        trimEnd ? this.string_.match(/\s*$/).index : this.string_.length;
-    return this.substring(trimmedStart, trimmedEnd);
-  }
-
-  /**
-   * Returns this spannable to a json serializable form, including the text
-   * and span objects whose types have been registered with
-   * registerSerializableSpan or registerStatelessSerializableSpan.
-   * @return {!SerializedSpannable} the json serializable form.
-   */
-  toJson() {
-    const result = {};
-    result.string = this.string_;
-    result.spans = [];
-    this.spans_.forEach(span => {
-      const serializeInfo =
-          serializableSpansByConstructor.get(span.value.constructor);
-      if (serializeInfo) {
-        const spanObj = {
-          type: serializeInfo.name,
-          start: span.start,
-          end: span.end,
-        };
-        if (serializeInfo.toJson) {
-          spanObj.value = serializeInfo.toJson.apply(span.value);
-        }
-        result.spans.push(spanObj);
-      }
-    });
-    return result;
-  }
-
-  /**
-   * Creates a spannable from a json serializable representation.
-   * @param {!SerializedSpannable} obj object containing the serializable
-   *     representation.
-   * @return {!Spannable}
-   */
-  static fromJson(obj) {
-    if (typeof obj.string !== 'string') {
-      throw new Error(
-          'Invalid spannable json object: string field not a string');
-    }
-    if (!(obj.spans instanceof Array)) {
-      throw new Error('Invalid spannable json object: no spans array');
-    }
-    const result = new Spannable(obj.string);
-    result.spans_ = obj.spans.map(span => {
-      if (typeof span.type !== 'string') {
-        throw new Error(
-            'Invalid span in spannable json object: type not a string');
-      }
-      if (typeof span.start !== 'number' || typeof span.end !== 'number') {
-        throw new Error(
-            'Invalid span in spannable json object: start or end not a number');
-      }
-      const serializeInfo = serializableSpansByName.get(span.type);
-      const value = serializeInfo.fromJson(span.value);
-      return {value, start: span.start, end: span.end};
-    });
-    return result;
-  }
-
-  /**
-   * Registers a type that can be converted to a json serializable format.
-   * @param {!Function} constructor The type of object that can be converted.
-   * @param {string} name String identifier used in the serializable format.
-   * @param {function(!Object): !Object} fromJson A function that converts
-   *     the serializable object to an actual object of this type.
-   * @param {function(): !Object} toJson A function that converts this object
-   *     to a json serializable object. The function will be called with
-   *     |this| set to the object to convert.
-   */
-  static registerSerializableSpan(constructor, name, fromJson, toJson) {
-    const obj = {name, fromJson, toJson};
-    serializableSpansByName.set(name, obj);
-    serializableSpansByConstructor.set(constructor, obj);
-  }
-
-  /**
-   * Registers an object type that can be converted to/from a json
-   * serializable form. Objects of this type carry no state that will be
-   * preserved when serialized.
-   * @param {!Function} constructor The type of the object that can be
-   *     converted. This constructor will be called with no arguments to
-   *     construct new objects.
-   * @param {string} name Name of the type used in the serializable object.
-   */
-  static registerStatelessSerializableSpan(constructor, name) {
-    const obj = {name, toJson: undefined};
-    /**
-     * @param {!Object} obj
-     * @return {!Object}
-     */
-    obj.fromJson = function(obj) {
-      return new constructor();
-    };
-    serializableSpansByName.set(name, obj);
-    serializableSpansByConstructor.set(constructor, obj);
-  }
-}
-
-
-/**
- * A spannable that allows a span value to annotate discontinuous regions of the
- * string. In effect, a span value can be set multiple times.
- * Note that most methods that assume a span value is unique such as
- * |getSpanStart| will use the first span value.
- */
-export class MultiSpannable extends Spannable {
-  /**
-   * @param {string|!Spannable=} opt_string Initial value of the spannable.
-   * @param {*=} opt_annotation Initial annotation for the entire string.
-   */
-  constructor(opt_string, opt_annotation) {
-    super(opt_string, opt_annotation);
-  }
-
-  /** @override */
-  setSpan(value, start, end) {
-    this.setSpanInternal(value, start, end);
-  }
-
-  /** @override */
-  substring(start, opt_end) {
-    const ret = Spannable.prototype.substring.call(this, start, opt_end);
-    return new MultiSpannable(ret);
-    }
-}
-
-
-/**
- * An annotation with its start and end points.
- * @typedef {{value: *, start: number, end: number}}
- */
-let SpanStruct;
-
-/**
- * Describes how to convert a span type to/from serializable json.
- * @typedef {{name: string,
- *            fromJson: function(!Object): !Object,
- *            toJson: ((function(): !Object)|undefined)}}
- */
-let SerializeInfo;
-
-/**
- * The serialized format of a spannable.
- * @typedef {{string: string, spans: Array<SerializedSpan>}}
- * @private
- */
-let SerializedSpannable;
-
-/**
- * The format of a single annotation in a serialized spannable.
- * @typedef {{type: string, value: !Object, start: number, end: number}}
- */
-let SerializedSpan;
-
-/**
- * Maps type names to serialization info objects.
- * @type {Map<string, SerializeInfo>}
- */
-const serializableSpansByName = new Map();
-
-/**
- * Maps constructors to serialization info objects.
- * @type {Map<Function, SerializeInfo>}
- */
-const serializableSpansByConstructor = new Map();
-
-// Helpers for implementing the various |get*| methods of |Spannable|.
-
-/**
- * @param {Function} constructor
- * @return {function(SpanStruct): boolean}
- */
-function spanInstanceOf(constructor) {
-  return function(span) {
-    return span.value instanceof constructor;
-  };
-}
-
-/**
- * @param {number} position
- * @return {function(SpanStruct): boolean}
- */
-function spanCoversPosition(position) {
-  return function(span) {
-    return span.start <= position && position < span.end;
-  };
-}
-
-/**
- * @param {*} value
- * @return {function(SpanStruct): boolean}
- */
-function spanValueIs(value) {
-  return function(span) {
-    return span.value === value;
-  };
-}
-
-/**
- * @param {!SpanStruct|undefined} span
- * @return {*}
- */
-function valueOfSpan(span) {
-  return span ? span.value : undefined;
-}
-
-TestImportManager.exportForTesting(Spannable, MultiSpannable);
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable.ts
new file mode 100644
index 0000000..8cc17d7
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable.ts
@@ -0,0 +1,476 @@
+// Copyright 2014 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Class which allows construction of annotated strings.
+ */
+import {TestImportManager} from '/common/testing/test_import_manager.js';
+
+type Annotation = any;
+
+interface Interval {
+  start: number;
+  end: number;
+}
+
+export class Spannable {
+  /** Underlying string. */
+  private string_: string;
+  /** Spans (annotations). */
+  private spans_: SpanStruct[] = [];
+
+  /**
+   * @param stringValue Initial value of the spannable.
+   * @param annotation Initial annotation for the entire string.
+   */
+  constructor(stringValue?: string|Spannable, annotation?: Annotation) {
+    this.string_ = stringValue instanceof Spannable ? '' : stringValue || '';
+
+    // Append the initial spannable.
+    if (stringValue instanceof Spannable) {
+      this.append(stringValue);
+    }
+
+    // Optionally annotate the entire string.
+    if (annotation !== undefined) {
+      const len = this.string_.length;
+      this.spans_.push({value: annotation, start: 0, end: len});
+    }
+  }
+
+  toString(): string {
+    return this.string_;
+  }
+
+  /** @return The length of the string */
+  get length(): number {
+    return this.string_.length;
+  }
+
+  /**
+   * Adds a span to some region of the string.
+   * @param value Annotation.
+   * @param start Starting index (inclusive).
+   * @param end Ending index (exclusive).
+   */
+  setSpan(value: Annotation, start: number, end: number): void {
+    this.removeSpan(value);
+    this.setSpanInternal(value, start, end);
+  }
+
+  /**
+   * @param value Annotation.
+   * @param start Starting index (inclusive).
+   * @param end Ending index (exclusive).
+   */
+  protected setSpanInternal(
+      value: Annotation, start: number, end: number): void {
+    if (0 <= start && start <= end && end <= this.string_.length) {
+      // Zero-length spans are explicitly allowed, because it is possible to
+      // query for position by annotation as well as the reverse.
+      this.spans_.push({value, start, end});
+      this.spans_.sort(function(a, b) {
+        let ret = a.start - b.start;
+        if (ret === 0) {
+          ret = a.end - b.end;
+        }
+        return ret;
+      });
+    } else {
+      throw new RangeError(
+          'span out of range (start=' + start + ', end=' + end +
+          ', len=' + this.string_.length + ')');
+    }
+  }
+
+  /**
+   * Removes a span.
+   * @param value Annotation.
+   */
+  removeSpan(value: Annotation): void {
+    for (let i = this.spans_.length - 1; i >= 0; i--) {
+      if (this.spans_[i].value === value) {
+        this.spans_.splice(i, 1);
+      }
+    }
+  }
+
+  /**
+   * Appends another Spannable or string to this one.
+   * @param other String or spannable to concatenate.
+   */
+  append(other: string | Spannable): void {
+    if (other instanceof Spannable) {
+      const otherSpannable = other as Spannable;
+      const originalLength = this.length;
+      this.string_ += otherSpannable.string_;
+      other.spans_.forEach(
+          span => this.setSpan(
+              span.value, span.start + originalLength,
+              span.end + originalLength));
+    } else if (typeof other === 'string') {
+      this.string_ += other;
+    }
+  }
+
+  /**
+   * Returns the first value matching a position.
+   * @param position Position to query.
+   * @return Value annotating that position, or undefined if none is
+   *     found.
+   */
+  getSpan(position: number): Annotation {
+    return valueOfSpan(this.spans_.find(spanCoversPosition(position)));
+  }
+
+  /**
+   * Returns the first span value which is an instance of a given constructor.
+   * @param constructor Constructor.
+   * @return Object if found; undefined otherwise.
+   */
+  getSpanInstanceOf(constructor: Function): Annotation {
+    return valueOfSpan(this.spans_.find(spanInstanceOf(constructor)));
+  }
+
+  /**
+   * Returns all span values which are an instance of a given constructor.
+   * Spans are returned in the order of their starting index and ending index
+   * for spans with equals tarting indices.
+   * @param constructor Constructor.
+   * @return Array of object.
+   */
+  getSpansInstanceOf(constructor: Function): Annotation[] {
+    return (this.spans_.filter(spanInstanceOf(constructor)).map(valueOfSpan));
+  }
+
+  /**
+   * Returns all spans matching a position.
+   * @param position Position to query.
+   * @return Values annotating that position.
+   */
+  getSpans(position: number): Annotation[] {
+    return (this.spans_.filter(spanCoversPosition(position)).map(valueOfSpan));
+  }
+
+  /**
+   * Returns whether a span is contained in this object.
+   * @param value Annotation.
+   */
+  hasSpan(value: Annotation): boolean {
+    return this.spans_.some(spanValueIs(value));
+  }
+
+  /**
+   * Returns the start of the requested span. Throws if the span doesn't exist
+   * in this object.
+   * @param value Annotation.
+   */
+  getSpanStart(value: Annotation): number {
+    return this.getSpanByValueOrThrow_(value).start;
+  }
+
+  /**
+   * Returns the end of the requested span. Throws if the span doesn't exist
+   * in this object.
+   * @param value Annotation.
+   */
+  getSpanEnd(value: Annotation): number {
+    return this.getSpanByValueOrThrow_(value).end;
+  }
+
+  /**
+   * @param value Annotation.
+   */
+  getSpanIntervals(value: Annotation): Interval[] {
+    return this.spans_.filter(span => span.value === value).map(span => {
+      return {start: span.start, end: span.end};
+    });
+  }
+
+  /**
+   * Returns the number of characters covered by the given span. Throws if
+   * the span is not in this object.
+   */
+  getSpanLength(value: Annotation): number {
+    const span = this.getSpanByValueOrThrow_(value);
+    return span.end - span.start;
+  }
+
+  /**
+   * Gets the internal object for a span or throws if the span doesn't exist.
+   * @param value The annotation.
+   */
+  private getSpanByValueOrThrow_(value: Annotation): SpanStruct {
+    const span = this.spans_.find(spanValueIs(value));
+    if (span) {
+      return span;
+    }
+    throw new Error('Span ' + value + ' doesn\'t exist in spannable');
+  }
+
+  /**
+   * Returns a substring of this spannable.
+   * Note that while similar to String#substring, this function is much less
+   * permissive about its arguments. It does not accept arguments in the wrong
+   * order or out of bounds.
+   *
+   * @param start Start index, inclusive.
+   * @param end End index, exclusive.
+   *     If excluded, the length of the string is used instead.
+   * @return Substring requested.
+   */
+  substring(start: number, end?: number): Spannable {
+    end = end !== undefined ? end : this.string_.length;
+
+    if (start < 0 || end > this.string_.length || start > end) {
+      throw new RangeError('substring indices out of range');
+    }
+
+    const result = new Spannable(this.string_.substring(start, end));
+    this.spans_.forEach(span => {
+      if (span.start <= end && span.end >= start) {
+        const newStart = Math.max(0, span.start - start);
+        const newEnd = Math.min(end - start, span.end - start);
+        result.spans_.push({value: span.value, start: newStart, end: newEnd});
+      }
+    });
+    return result;
+  }
+
+  /**
+   * Trims whitespace from the beginning.
+   * @return String with whitespace removed.
+   */
+  trimLeft(): Spannable {
+    return this.trim_(true, false);
+  }
+
+  /**
+   * Trims whitespace from the end.
+   * @return String with whitespace removed.
+   */
+  trimRight(): Spannable {
+    return this.trim_(false, true);
+  }
+
+  /**
+   * Trims whitespace from the beginning and end.
+   * @return String with whitespace removed.
+   */
+  trim(): Spannable {
+    return this.trim_(true, true);
+  }
+
+  /**
+   * Trims whitespace from either the beginning and end or both.
+   * @param trimStart Trims whitespace from the start of a string.
+   * @param trimEnd Trims whitespace from the end of a string.
+   * @return String with whitespace removed.
+   */
+  private trim_(trimStart: boolean, trimEnd: boolean): Spannable {
+    if (!trimStart && !trimEnd) {
+      return this;
+    }
+
+    // Special-case whitespace-only strings, including the empty string.
+    // As an arbitrary decision, we treat this as trimming the whitespace off
+    // the end, rather than the beginning, of the string.
+    // This choice affects which spans are kept.
+    if (/^\s*$/.test(this.string_)) {
+      return this.substring(0, 0);
+    }
+
+    // Otherwise, we have at least one non-whitespace character to use as an
+    // anchor when trimming.
+    // TODO(b/314203187): Not null asserted, check that this is correct.
+    const trimmedStart = trimStart ? this.string_.match(/^\s*/)![0].length : 0;
+    const trimmedEnd =
+        trimEnd ? this.string_.match(/\s*$/)!.index : this.string_.length;
+    return this.substring(trimmedStart, trimmedEnd);
+  }
+
+  /**
+   * Returns this spannable to a json serializable form, including the text
+   * and span objects whose types have been registered with
+   * registerSerializableSpan or registerStatelessSerializableSpan.
+   * @return the json serializable form.
+   */
+  toJson(): SerializedSpannable {
+    const spans: SerializedSpan[] = [];
+    this.spans_.forEach(span => {
+      const serializeInfo =
+          serializableSpansByConstructor.get(span.value.constructor);
+      if (serializeInfo) {
+        const spanObj: SerializedSpan = {
+          type: serializeInfo.name,
+          start: span.start,
+          end: span.end,
+          value: undefined,
+        };
+        if (serializeInfo.toJson) {
+          spanObj.value = serializeInfo.toJson.apply(span.value);
+        }
+        spans.push(spanObj);
+      }
+    });
+    return {string: this.string_, spans};
+  }
+
+  /**
+   * Creates a spannable from a json serializable representation.
+   * @param obj object containing the serializable representation.
+   */
+  static fromJson(obj: SerializedSpannable): Spannable {
+    if (typeof obj.string !== 'string') {
+      throw new Error(
+          'Invalid spannable json object: string field not a string');
+    }
+    if (!(obj.spans instanceof Array)) {
+      throw new Error('Invalid spannable json object: no spans array');
+    }
+    const result = new Spannable(obj.string);
+    result.spans_ = obj.spans.map(span => {
+      if (typeof span.type !== 'string') {
+        throw new Error(
+            'Invalid span in spannable json object: type not a string');
+      }
+      if (typeof span.start !== 'number' || typeof span.end !== 'number') {
+        throw new Error(
+            'Invalid span in spannable json object: start or end not a number');
+      }
+      // TODO(b/314203187): Not null asserted, check that this is correct.
+      const serializeInfo = serializableSpansByName.get(span.type)!;
+      const value = serializeInfo.fromJson(span.value);
+      return {value, start: span.start, end: span.end};
+    });
+    return result;
+  }
+
+  /**
+   * Registers a type that can be converted to a json serializable format.
+   * @param constructor The type of object that can be converted.
+   * @param name String identifier used in the serializable format.
+   * @param fromJson A function that converts the serializable object to an
+   *     actual object of this type.
+   * @param toJson A function that converts this object to a json serializable
+   *     object. The function will be called with |this| set to the object to
+   *     convert.
+   */
+  static registerSerializableSpan(
+      constructor: Function, name: string,
+      fromJson: (json: SerializedSpan) => Annotation,
+      toJson: () => SerializedSpan): void {
+    const obj: SerializeInfo = {name, fromJson, toJson};
+    serializableSpansByName.set(name, obj);
+    serializableSpansByConstructor.set(constructor, obj);
+  }
+
+  /**
+   * Registers an object type that can be converted to/from a json
+   * serializable form. Objects of this type carry no state that will be
+   * preserved when serialized.
+   * @param constructor The type of the object that can be converted. This
+   *     constructor will be called with no arguments to construct new objects.
+   * @param name Name of the type used in the serializable object.
+   */
+  static registerStatelessSerializableSpan(
+      constructor: Function, name: string): void {
+    const fromJson = function(_obj: SerializedSpan): Annotation {
+      return new (constructor as FunctionConstructor)();
+    };
+    const obj: SerializeInfo = {name, toJson: undefined, fromJson};
+    serializableSpansByName.set(name, obj);
+    serializableSpansByConstructor.set(constructor, obj);
+  }
+}
+
+
+/**
+ * A spannable that allows a span value to annotate discontinuous regions of the
+ * string. In effect, a span value can be set multiple times.
+ * Note that most methods that assume a span value is unique such as
+ * |getSpanStart| will use the first span value.
+ */
+export class MultiSpannable extends Spannable {
+  /**
+   * @param string Initial value of the spannable.
+   * @param annotation Initial annotation for the entire string.
+   */
+  constructor(string?: string | Spannable, annotation?: Annotation) {
+    super(string, annotation);
+  }
+
+  override setSpan(value: Annotation, start: number, end: number): void {
+    this.setSpanInternal(value, start, end);
+  }
+
+  override substring(start: number, end: number): MultiSpannable {
+    const ret = Spannable.prototype.substring.call(this, start, end);
+    return new MultiSpannable(ret);
+    }
+}
+
+// Local to module.
+
+/** An annotation with its start and end points. */
+interface SpanStruct {
+  value: Annotation;
+  start: number;
+  end: number;
+}
+
+/** Describes how to convert a span type to/from serializable json. */
+interface SerializeInfo {
+  name: string;
+  fromJson: (json: SerializedSpan) => Annotation;
+  toJson?: () => SerializedSpan;
+}
+
+/** The serialized format of a spannable. */
+interface SerializedSpannable {
+  string: string;
+  spans: SerializedSpan[];
+}
+
+/** The format of a single annotation in a serialized spannable. */
+interface SerializedSpan {
+  type: string;
+  value: Annotation;
+  start: number;
+  end: number;
+}
+
+type SpanPredicate = (span: SpanStruct) => boolean;
+
+/** Maps type names to serialization info objects. */
+const serializableSpansByName: Map<string, SerializeInfo> = new Map();
+
+/** Maps constructors to serialization info objects. */
+const serializableSpansByConstructor: Map<Function, SerializeInfo> = new Map();
+
+// Helpers for implementing the various |get*| methods of |Spannable|.
+
+function spanInstanceOf(constructor: Function): SpanPredicate {
+  return function(span) {
+    return span.value instanceof constructor;
+  };
+}
+
+function spanCoversPosition(position: number): SpanPredicate {
+  return function(span) {
+    return span.start <= position && position < span.end;
+  };
+}
+
+function spanValueIs(value: Annotation): SpanPredicate {
+  return function(span) {
+    return span.value === value;
+  };
+}
+
+function valueOfSpan(span?: SpanStruct): Annotation {
+  return span ? span.value : undefined;
+}
+
+TestImportManager.exportForTesting(Spannable, MultiSpannable);
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.ts
index b76cfd05..51d9c91 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.ts
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/menu_manager.ts
@@ -343,15 +343,16 @@
     // commands for touch).
     const keymap = KeyMap.get();
 
-    const sortedBindings = keymap.bindings().slice();
+    // A shallow copy of the bindings is returned, so re-ordering the elements
+    // does not change the original.
+    const sortedBindings = keymap.bindings();
     for (const binding of sortedBindings) {
       const command = binding.command;
       const keySeq = binding.sequence;
       binding.keySeq = await KeyUtil.keySequenceToString(keySeq, true);
       const titleMsgId = CommandStore.messageForCommand(command);
       if (!titleMsgId) {
-        // Title messages are intentionally missing for some keyboard
-        // shortcuts.
+        // Title messages are intentionally missing for some keyboard shortcuts.
         if (!(command in COMMANDS_WITH_NO_MSG_ID) &&
             !MenuManager.disableMissingMsgsErrorsForTesting) {
           console.error('No localization for: ' + command);
@@ -368,9 +369,17 @@
     return sortedBindings;
   }
 
+  makeBindingMap(sortedBindings: KeyBinding[]): Map<Command, KeyBinding> {
+    const bindingMap = new Map();
+    for (const binding of sortedBindings) {
+      bindingMap.set(binding.command, binding);
+    }
+    return bindingMap;
+  }
+
   makeCategoryMapping(
       actionsMenu: PanelMenu, chromevoxMenu: PanelMenu, jumpMenu: PanelMenu,
-      speechMenu: PanelMenu): Record<CommandCategory, PanelMenu | null> {
+      speechMenu: PanelMenu): Record<CommandCategory, PanelMenu|null> {
     return {
       [CommandCategory.ACTIONS]: actionsMenu,
       [CommandCategory.BRAILLE]: null,
@@ -387,12 +396,34 @@
     };
   }
 
-  makeBindingMap(sortedBindings: KeyBinding[]): Map<Command, KeyBinding> {
-    const bindingMap = new Map();
-    for (const binding of sortedBindings) {
-      bindingMap.set(binding.command, binding);
+  /**
+   * Called when the user releases the mouse button. If it's anywhere other
+   * than on the menus button, close the menus and return focus to the page,
+   * and if the mouse was released over a menu item, execute that item's
+   * callback.
+   */
+  onMouseUp(event: MouseEvent): void {
+    if (!this.activeMenu_) {
+      return;
     }
-    return bindingMap;
+
+    let target: HTMLElement|null = event.target as HTMLElement;
+    while (target && !target.classList.contains('menu-item')) {
+      // Allow the user to click and release on the menu button and leave
+      // the menu button.
+      if (target.id === 'menus_button') {
+        return;
+      }
+
+      target = target.parentElement;
+    }
+
+    // TODO(b/314203187): Not null asserted, check that this is correct.
+    if (target && this.activeMenu_) {
+      PanelInterface.instance!.setPendingCallback(
+          this.activeMenu_.getCallbackForElement(target));
+    }
+    PanelInterface.instance!.closeMenusAndRestoreFocus();
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.ts
index 0889675..7cad9434 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.ts
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.ts
@@ -76,7 +76,8 @@
     document.addEventListener(
         'keydown', (event: KeyboardEvent) => this.onKeyDown_(event), false);
     document.addEventListener(
-        'mouseup', (event: MouseEvent) => this.onMouseUp_(event), false);
+        'mouseup', (event: MouseEvent) => this.menuManager_.onMouseUp(event),
+        false);
     window.addEventListener(
         'storage', (event: StorageEvent) => this.onStorageChanged_(event),
         false);
@@ -416,35 +417,6 @@
   }
 
   /**
-   * Called when the user releases the mouse button. If it's anywhere other
-   * than on the menus button, close the menus and return focus to the page,
-   * and if the mouse was released over a menu item, execute that item's
-   * callback.
-   */
-  private onMouseUp_(event: Event): void {
-    if (!this.menuManager_.activeMenu) {
-      return;
-    }
-
-    let target = event.target as HTMLElement | null;
-    while (target && !target.classList.contains('menu-item')) {
-      // Allow the user to click and release on the menu button and leave
-      // the menu button.
-      if (target.id === 'menus_button') {
-        return;
-      }
-
-      target = target.parentElement;
-    }
-
-    if (target && this.menuManager_.activeMenu) {
-      this.pendingCallback_ =
-          this.menuManager_.activeMenu.getCallbackForElement(target);
-    }
-    this.closeMenusAndRestoreFocus();
-  }
-
-  /**
    * Called when a key is pressed. Handle arrow keys to navigate the menus,
    * Esc to close, and Enter/Space to activate an item.
    */
diff --git a/chrome/browser/resources/chromeos/login/components/oobe_categories_list.ts b/chrome/browser/resources/chromeos/login/components/oobe_categories_list.ts
index f5a66750..e1ada9b 100644
--- a/chrome/browser/resources/chromeos/login/components/oobe_categories_list.ts
+++ b/chrome/browser/resources/chromeos/login/components/oobe_categories_list.ts
@@ -104,6 +104,12 @@
     this.selectedCategoriesCount = 0;
     this.loadedIconsCount = 0;
     this.itemRendered = 0;
+    this.categoriesList.forEach((category) => {
+      if (category.selected) {
+        this.selectedCategoriesCount++;
+        this.categoriesSelected.push(category.categoryId);
+      }
+    });
   }
 
   itemRenderedChanged(): void {
diff --git a/chrome/browser/resources/chromeos/password_change/BUILD.gn b/chrome/browser/resources/chromeos/password_change/BUILD.gn
index 78e8f024..c17b8f2 100644
--- a/chrome/browser/resources/chromeos/password_change/BUILD.gn
+++ b/chrome/browser/resources/chromeos/password_change/BUILD.gn
@@ -50,6 +50,8 @@
   in_files = [
     "confirm_password_change.html",
     "confirm_password_change.js",
+    "confirm_password_change_app.html",
+    "confirm_password_change_app.js",
     "password_change.js",
     "password_change_app.html",
     "password_change_app.js",
@@ -64,6 +66,7 @@
   out_folder = preprocess_folder
   out_manifest = gen_files_manifest
   in_files = [
+    "confirm_password_change.html.js",
     "password_change.html.js",
     "urgent_password_expiry_notification_app.html.js",
   ]
@@ -71,6 +74,7 @@
 
 html_to_wrapper("html_wrapper_files") {
   in_files = [
+    "confirm_password_change.html",
     "password_change.html",
     "urgent_password_expiry_notification_app.html",
   ]
diff --git a/chrome/browser/resources/chromeos/password_change/confirm_password_change.html b/chrome/browser/resources/chromeos/password_change/confirm_password_change.html
index 60b963d..26481a6 100644
--- a/chrome/browser/resources/chromeos/password_change/confirm_password_change.html
+++ b/chrome/browser/resources/chromeos/password_change/confirm_password_change.html
@@ -1,105 +1,84 @@
-<!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
-<head>
-  <meta charset="utf-8">
+<style>
+  ::part(dialog) {
+    /* The HTML dialog should fill the entire system dialog. */
+    height: 100%;
+    width: 100%;
+  }
 
-  <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
+  [slot='title'] {
+    color: black;
+    font-family: Roboto, sans-serif;
+    font-size: 15px;
+    padding: 24px 24px 16px;
+  }
 
+  [slot='body'] {
+    color: var(--google-grey-900);
+    font-family: Roboto, sans-serif;
+    font-size: 13px;
+    padding: 0 48px 0 24px;
+  }
 
-  <script  type="module" src="confirm_password_change.js"></script>
+  [slot='button-container'] {
+    bottom: 0;
+    box-sizing: border-box;
+    margin: 0;
+    padding: 16px;
+    position: fixed;
+    width: 100%;
+  }
 
-  <dom-module id="confirm-password-change">
-    <template>
-      <style>
-        ::part(dialog) {
-          /* The HTML dialog should fill the entire system dialog. */
-          height: 100%;
-          width: 100%;
-        }
+  #prompt {
+    margin-bottom: 20px;
+  }
 
-        [slot='title'] {
-          color: black;
-          font-family: Roboto, sans-serif;
-          font-size: 15px;
-          padding: 24px 24px 16px;
-        }
+  cr-input[type='password'] {
+    font-size: 20px;
+  }
 
-        [slot='body'] {
-          color: var(--google-grey-900);
-          font-family: Roboto, sans-serif;
-          font-size: 13px;
-          padding: 0 48px 0 24px;
-        }
+  paper-spinner-lite {
+    height: 44px;
+    left: 50%;
+    margin: -22px;
+    position: fixed;
+    top: 50%;
+    width: 44px;
+  }
+</style>
 
-        [slot='button-container'] {
-          bottom: 0;
-          box-sizing: border-box;
-          margin: 0;
-          padding: 16px;
-          position: fixed;
-          width: 100%;
-        }
+<paper-spinner-lite active></paper-spinner-lite>
 
-        #prompt {
-          margin-bottom: 20px;
-        }
+<cr-dialog id="dialog" exportparts="dialog">
+  <div slot="title">[[i18n('title')]]</div>
 
-        cr-input[type='password'] {
-          font-size: 20px;
-        }
+  <div slot="body" spellcheck="false">
+    <div id="prompt">[[promptString_]]</div>
 
-        paper-spinner-lite {
-          height: 44px;
-          left: 50%;
-          margin: -22px;
-          position: fixed;
-          top: 50%;
-          width: 44px;
-        }
-      </style>
+    <div hidden="[[!showOldPasswordPrompt_]]">
+      <cr-input type="password" value="{{oldPassword_}}"
+          label="[[i18n('oldPassword')]]"
+          invalid="[[invalidOldPassword_(currentValidationError_)]]"
+          error-message="[[errorString_]]">
+      </cr-input>
+    </div>
 
-     <paper-spinner-lite active></paper-spinner-lite>
+    <div hidden="[[!showNewPasswordPrompt_]]">
+      <cr-input type="password" value="{{newPassword_}}"
+          label="[[i18n('newPassword')]]"
+          invalid="[[invalidNewPassword_(currentValidationError_)]]"
+          error-message="[[errorString_]]">
+      </cr-input>
+      <cr-input type="password" value="{{confirmNewPassword_}}"
+          label="[[i18n('confirmNewPassword')]]"
+          invalid="[[invalidConfirmNewPassword_(currentValidationError_)]]"
+          error-message="[[errorString_]]">
+      </cr-input>
+    </div>
+  </div>
 
-      <cr-dialog id="dialog" exportparts="dialog">
-        <div slot="title">[[i18n('title')]]</div>
-
-        <div slot="body" spellcheck="false">
-          <div id="prompt">[[promptString_]]</div>
-
-          <div hidden="[[!showOldPasswordPrompt_]]">
-            <cr-input type="password" value="{{oldPassword_}}"
-                label="[[i18n('oldPassword')]]"
-                invalid="[[invalidOldPassword_(currentValidationError_)]]"
-                error-message="[[errorString_]]">
-            </cr-input>
-          </div>
-
-          <div hidden="[[!showNewPasswordPrompt_]]">
-            <cr-input type="password" value="{{newPassword_}}"
-                label="[[i18n('newPassword')]]"
-                invalid="[[invalidNewPassword_(currentValidationError_)]]"
-                error-message="[[errorString_]]">
-            </cr-input>
-            <cr-input type="password" value="{{confirmNewPassword_}}"
-                label="[[i18n('confirmNewPassword')]]"
-                invalid=
-                    "[[invalidConfirmNewPassword_(currentValidationError_)]]"
-                error-message="[[errorString_]]">
-            </cr-input>
-          </div>
-        </div>
-
-        <div slot="button-container">
-          <cr-button class="action-button" on-click="onSaveTap_">
-            [[i18n('save')]]
-          </cr-button>
-        </div>
-      </cr-dialog>
-    </template>
-  </dom-module>
-</head>
-
-<body>
-  <confirm-password-change id="main-element"></confirm-password-change>
-</body>
-</html>
+  <div slot="button-container">
+    <cr-button class="action-button" on-click="onSaveTap_">
+      [[i18n('save')]]
+    </cr-button>
+  </div>
+</cr-dialog>
diff --git a/chrome/browser/resources/chromeos/password_change/confirm_password_change.js b/chrome/browser/resources/chromeos/password_change/confirm_password_change.js
index 7e622d9..0737e7d 100644
--- a/chrome/browser/resources/chromeos/password_change/confirm_password_change.js
+++ b/chrome/browser/resources/chromeos/password_change/confirm_password_change.js
@@ -17,16 +17,18 @@
 
 import 'chrome://confirm-password-change/strings.m.js';
 import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
-import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
 import 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
 import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 
 import {sendWithPromise} from 'chrome://resources/ash/common/cr.m.js';
-import {I18nBehavior} from 'chrome://resources/ash/common/i18n_behavior.js';
+import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {WebUIListenerBehavior} from 'chrome://resources/ash/common/web_ui_listener_behavior.js';
-import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './confirm_password_change.html.js';
 
 /** @enum{number} */
 const ValidationErrorType = {
@@ -38,62 +40,91 @@
   INCORRECT_OLD_PASSWORD: 5,
 };
 
-Polymer({
-  is: 'confirm-password-change',
+/**
+ * @constructor
+ * @extends {PolymerElement}
+ * @implements {I18nBehaviorInterface}
+ */
+const ConfirmPasswordChangeElementBase =
+    mixinBehaviors([I18nBehavior, WebUIListenerBehavior], PolymerElement);
 
-  behaviors: [I18nBehavior, WebUIListenerBehavior],
+/**
+ * @typedef {{
+ *   dialog: CrDialogElement,
+ * }}
+ */
+ConfirmPasswordChangeElementBase.$;
 
-  properties: {
-    /** @private {boolean} */
-    showSpinner_:
-        {type: Boolean, value: true, observer: 'onShowSpinnerChanged_'},
+/** @polymer */
+class ConfirmPasswordChangeElement extends ConfirmPasswordChangeElementBase {
+  static get is() {
+    return 'confirm-password-change';
+  }
 
-    /** @private {boolean} */
-    showOldPasswordPrompt_: {type: Boolean, value: true},
+  static get template() {
+    return getTemplate();
+  }
 
-    /** @private {string} */
-    oldPassword_: {type: String, value: ''},
+  static get properties() {
+    return {
+      /** @private {boolean} */
+      showSpinner_:
+          {type: Boolean, value: true, observer: 'onShowSpinnerChanged_'},
 
-    /** @private {boolean} */
-    showNewPasswordPrompt_: {type: Boolean, value: true},
+      /** @private {boolean} */
+      showOldPasswordPrompt_: {type: Boolean, value: true},
 
-    /** @private {string} */
-    newPassword_: {type: String, value: ''},
+      /** @private {string} */
+      oldPassword_: {type: String, value: ''},
 
-    /** @private {string} */
-    confirmNewPassword_: {type: String, value: ''},
+      /** @private {boolean} */
+      showNewPasswordPrompt_: {type: Boolean, value: true},
 
-    /** @private {!ValidationErrorType} */
-    currentValidationError_: {
-      type: Number,
-      value: ValidationErrorType.NO_ERROR,
-      observer: 'onErrorChanged_',
-    },
+      /** @private {string} */
+      newPassword_: {type: String, value: ''},
 
-    /** @private {string} */
-    promptString_: {
-      type: String,
-      computed:
-          'getPromptString_(showOldPasswordPrompt_, showNewPasswordPrompt_)',
-    },
+      /** @private {string} */
+      confirmNewPassword_: {type: String, value: ''},
 
-    /** @private {string} */
-    errorString_:
-        {type: String, computed: 'getErrorString_(currentValidationError_)'},
-  },
+      /** @private {!ValidationErrorType} */
+      currentValidationError_: {
+        type: Number,
+        value: ValidationErrorType.NO_ERROR,
+        observer: 'onErrorChanged_',
+      },
 
-  observers: [
-    'onShowPromptChanged_(showOldPasswordPrompt_, showNewPasswordPrompt_)',
-  ],
+      /** @private {string} */
+      promptString_: {
+        type: String,
+        computed:
+            'getPromptString_(showOldPasswordPrompt_, showNewPasswordPrompt_)',
+      },
+
+      /** @private {string} */
+      errorString_:
+          {type: String, computed: 'getErrorString_(currentValidationError_)'},
+
+    };
+  }
+
+  static get observers() {
+    return [
+      'onShowPromptChanged_(showOldPasswordPrompt_, showNewPasswordPrompt_)',
+
+    ];
+  }
+
 
   /** @override */
-  attached() {
+  connectedCallback() {
+    super.connectedCallback();
+
     this.addWebUIListener('incorrect-old-password', () => {
       this.onIncorrectOldPassword_();
     });
 
     this.getInitialState_();
-  },
+  }
 
   /** @private */
   getInitialState_() {
@@ -102,7 +133,7 @@
       this.showNewPasswordPrompt_ = result.showNewPasswordPrompt;
       this.showSpinner_ = result.showSpinner;
     });
-  },
+  }
 
 
   /** @private */
@@ -113,7 +144,7 @@
     } else {
       this.$.dialog.showModal();
     }
-  },
+  }
 
   /** @private */
   onShowPromptChanged_() {
@@ -123,14 +154,14 @@
     const height = loadTimeData.getInteger('height' + suffix);
 
     window.resizeTo(width, height);
-  },
+  }
 
   /** @private */
   onErrorChanged_() {
     if (this.currentValidationError_ !== ValidationErrorType.NO_ERROR) {
       this.showSpinner_ = false;
     }
-  },
+  }
 
   /** @private */
   onSaveTap_() {
@@ -139,7 +170,7 @@
       chrome.send('changePassword', [this.oldPassword_, this.newPassword_]);
       this.showSpinner_ = true;
     }
-  },
+  }
 
   /** @private */
   onIncorrectOldPassword_() {
@@ -154,7 +185,7 @@
       this.showOldPasswordPrompt_ = true;
       this.currentValidationError_ = ValidationErrorType.MISSING_OLD_PASSWORD;
     }
-  },
+  }
 
   /**
    * @return {!ValidationErrorType}
@@ -178,7 +209,7 @@
       }
     }
     return ValidationErrorType.NO_ERROR;
-  },
+  }
 
   /**
    * @return {boolean}
@@ -188,7 +219,7 @@
     const err = this.currentValidationError_;
     return err === ValidationErrorType.MISSING_OLD_PASSWORD ||
         err === ValidationErrorType.INCORRECT_OLD_PASSWORD;
-  },
+  }
 
   /**
    * @return {boolean}
@@ -197,7 +228,7 @@
   invalidNewPassword_() {
     return this.currentValidationError_ ===
         ValidationErrorType.MISSING_NEW_PASSWORD;
-  },
+  }
 
   /**
    * @return {boolean}
@@ -207,7 +238,7 @@
     const err = this.currentValidationError_;
     return err === ValidationErrorType.MISSING_CONFIRM_NEW_PASSWORD ||
         err === ValidationErrorType.PASSWORDS_DO_NOT_MATCH;
-  },
+  }
 
   /**
    * @return {string}
@@ -224,7 +255,7 @@
       return this.i18n('newPasswordPrompt');
     }
     return '';
-  },
+  }
 
   /**
    * @return {string}
@@ -239,5 +270,8 @@
       default:
         return '';
     }
-  },
-});
+  }
+}
+
+customElements.define(
+    ConfirmPasswordChangeElement.is, ConfirmPasswordChangeElement);
diff --git a/chrome/browser/resources/chromeos/password_change/confirm_password_change_app.html b/chrome/browser/resources/chromeos/password_change/confirm_password_change_app.html
new file mode 100644
index 0000000..2ab818f
--- /dev/null
+++ b/chrome/browser/resources/chromeos/password_change/confirm_password_change_app.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<head>
+  <meta charset="utf-8">
+  <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
+</head>
+<body>
+  <script type="module" src="confirm_password_change.js"></script>
+  <confirm-password-change id="main-element"></confirm-password-change>
+</body>
+</html>
diff --git a/chrome/browser/resources/chromeos/password_change/confirm_password_change_app.js b/chrome/browser/resources/chromeos/password_change/confirm_password_change_app.js
new file mode 100644
index 0000000..2b7d3b4
--- /dev/null
+++ b/chrome/browser/resources/chromeos/password_change/confirm_password_change_app.js
@@ -0,0 +1,18 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import './strings.m.js';
+import './confirm_password_change.js';
+
+import {$} from 'chrome://resources/ash/common/util.js';
+
+function initialize() {
+  // '$(id)' is an alias for 'document.getElementById(id)'. It is defined
+  // in chrome://resources/ash/common/util.js. If this function is not exposed
+  // via the global object, it would not be available to tests that inject
+  // JavaScript directly into the renderer.
+  window.$ = $;
+}
+
+initialize();
diff --git a/chrome/browser/resources/compose/app.ts b/chrome/browser/resources/compose/app.ts
index 7cdd75a..9e50910 100644
--- a/chrome/browser/resources/compose/app.ts
+++ b/chrome/browser/resources/compose/app.ts
@@ -67,6 +67,7 @@
     redoButton: CrButtonElement,
     refreshButton: HTMLElement,
     resultContainer: HTMLElement,
+    resultTextContainer: HTMLElement,
     resultFooter: HTMLElement,
     submitButton: CrButtonElement,
     submitEditButton: CrButtonElement,
diff --git a/chrome/browser/resources/management/management_browser_proxy.ts b/chrome/browser/resources/management/management_browser_proxy.ts
index 704aad9..f06ba403 100644
--- a/chrome/browser/resources/management/management_browser_proxy.ts
+++ b/chrome/browser/resources/management/management_browser_proxy.ts
@@ -124,6 +124,11 @@
    * @return The list of browser reporting info messages.
    */
   initBrowserReportingInfo(): Promise<BrowserReportingResponse[]>;
+
+  /**
+   * @return The list of profile reporting info messages.
+   */
+  initProfileReportingInfo(): Promise<BrowserReportingResponse[]>;
 }
 
 export class ManagementBrowserProxyImpl implements ManagementBrowserProxy {
@@ -165,6 +170,10 @@
     return sendWithPromise('initBrowserReportingInfo');
   }
 
+  initProfileReportingInfo() {
+    return sendWithPromise('initProfileReportingInfo');
+  }
+
   static getInstance(): ManagementBrowserProxy {
     return instance || (instance = new ManagementBrowserProxyImpl());
   }
diff --git a/chrome/browser/resources/management/management_ui.html b/chrome/browser/resources/management/management_ui.html
index 8c169dc..a4653318 100644
--- a/chrome/browser/resources/management/management_ui.html
+++ b/chrome/browser/resources/management/management_ui.html
@@ -67,7 +67,7 @@
         width: 40px;
       }
 
-      .eol-section iron-icon {
+      .eol-section cr-icon {
         --iron-icon-fill-color: #E8710A;
         height: var(--cr-icon-size);
         width: var(--cr-icon-size);
@@ -130,7 +130,7 @@
         margin: 0;
       }
 
-      .report iron-icon {
+      .report cr-icon {
         height: 20px;
         margin-inline-end: 16px;
         width: 20px;
@@ -196,11 +196,12 @@
 
       .extension-permissions ul,
       .application-permissions ul,
-      .report ul {
+      .report ul.browser {
         list-style: none;
         margin: 0;
         padding: 0;
       }
+
     </style>
 
     <cr-toolbar page-name="$i18n{toolbarTitle}" role="banner" autofocus
@@ -223,7 +224,7 @@
 <if expr="chromeos_ash">
           <section class="eol-section" hidden="[[!eolMessage_]]">
             <div class="eol-warning-icon">
-              <iron-icon icon="cr20:banner-warning"></iron-icon>
+              <cr-icon icon="cr20:banner-warning"></cr-icon>
             </div>
             <div class="eol-message">
               <div>[[eolMessage_]]</div>
@@ -297,8 +298,8 @@
               <div class="content-indented">
                 <template is="dom-repeat" items="[[deviceReportingInfo_]]">
                   <div class="report">
-                    <iron-icon icon="[[getIconForDeviceReportingType_(
-                        item.reportingType)]]"></iron-icon>
+                    <cr-icon icon="[[getIconForDeviceReportingType_(
+                        item.reportingType)]]"></cr-icon>
                     <div
                       inner-h-t-m-l="[[getDeviceReportingHtmlContent_(item)]]">
                     </div>
@@ -323,8 +324,8 @@
               <div>
                 <template is="dom-repeat" items="[[browserReportingInfo_]]">
                   <div class="report">
-                    <iron-icon icon="[[item.icon]]"></iron-icon>
-                    <ul>
+                    <cr-icon icon="[[item.icon]]"></cr-icon>
+                    <ul class="browser">
                       <template is="dom-repeat" items="[[item.messageIds]]"
                           as="messageId">
                         <li inner-h-t-m-l="[[i18nAdvanced(messageId)]]"></li>
@@ -335,6 +336,26 @@
               </div>
             </section>
           </template>
+
+          <template is="dom-if"
+              if="[[showProfileReportingInfo_(profileReportingInfo_)]]">
+            <section>
+              <h2 class="cr-title-text">$i18n{browserReporting}</h2>
+              <div class="subtitle">
+                $i18n{profileReportingExplanation}
+              </div>
+              <div>
+                <div class="report">
+                  <ul class="profile">
+                    <template is="dom-repeat" items="[[profileReportingInfo_]]">
+                      <li inner-h-t-m-l="[[i18nAdvanced(item.messageIds.0)]]">
+                      </li>
+                    </template>
+                  </ul>
+                </div>
+              </div>
+            </section>
+          </template>
 </if>
           <template is="dom-if"
               if="[[showExtensionReportingInfo_(extensions_)]]">
diff --git a/chrome/browser/resources/management/management_ui.ts b/chrome/browser/resources/management/management_ui.ts
index c5124f6..a66ab93f 100644
--- a/chrome/browser/resources/management/management_ui.ts
+++ b/chrome/browser/resources/management/management_ui.ts
@@ -5,9 +5,10 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_icons.css.js';
 import 'chrome://resources/cr_elements/cr_page_host_style.css.js';
+import 'chrome://resources/cr_elements/cr_icon/cr_icon.js';
 import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
 import 'chrome://resources/cr_elements/cr_hidden_style.css.js';
-import 'chrome://resources/cr_elements/icons.html.js';
+import 'chrome://resources/cr_elements/icons_lit.html.js';
 import 'chrome://resources/cr_elements/cr_shared_style.css.js';
 import './icons.html.js';
 import './strings.m.js';
@@ -30,7 +31,7 @@
 
 interface BrowserReportingData {
   messageIds: string[];
-  icon: string;
+  icon?: string;
 }
 
 const ManagementUiElementBase = WebUiListenerMixin(I18nMixin(PolymerElement));
@@ -62,6 +63,11 @@
       browserReportingInfo_: Array,
 
       /**
+       * List of messages related to profile reporting.
+       */
+      profileReportingInfo_: Array,
+
+      /**
        * List of messages related to extension reporting.
        */
       extensions_: Array,
@@ -110,6 +116,8 @@
 
   private applications_: Application[]|null;
   private browserReportingInfo_: BrowserReportingData[]|null;
+  private profileReportingInfo_: BrowserReportingData[]|null;
+  private reportingInfo_: BrowserReportingData[]|null;
   private extensions_: Extension[]|null;
   private managedWebsites_: string[]|null;
   private managedWebsitesSubtitle_: string;
@@ -144,7 +152,7 @@
     document.documentElement.classList.remove('loading');
     this.browserProxy_ = ManagementBrowserProxyImpl.getInstance();
     this.updateManagedFields_();
-    this.initBrowserReportingInfo_();
+    this.initReportingInfo_();
     this.getThreatProtectionInfo_();
 
     this.addWebUiListener(
@@ -152,6 +160,11 @@
         (reportingInfo: BrowserReportingResponse[]) =>
             this.onBrowserReportingInfoReceived_(reportingInfo));
 
+    this.addWebUiListener(
+        'profile-reporting-info-updated',
+        (reportingInfo: BrowserReportingResponse[]) =>
+            this.onProfileReportingInfoReceived_(reportingInfo));
+
     // <if expr="is_chromeos">
     this.addWebUiListener(
         'plugin-vm-data-collection-updated',
@@ -176,9 +189,11 @@
     // </if>
   }
 
-  private initBrowserReportingInfo_() {
+  private initReportingInfo_() {
     this.browserProxy_!.initBrowserReportingInfo().then(
         reportingInfo => this.onBrowserReportingInfoReceived_(reportingInfo));
+    this.browserProxy_!.initProfileReportingInfo().then(
+        reportingInfo => this.onProfileReportingInfoReceived_(reportingInfo));
   }
 
   private onBrowserReportingInfoReceived_(reportingInfo:
@@ -207,6 +222,14 @@
             .map(reportingType => reportingInfoMap[reportingType]);
   }
 
+
+  private onProfileReportingInfoReceived_(reportingInfo:
+                                              BrowserReportingResponse[]) {
+    this.profileReportingInfo_ =
+        reportingInfo.map((info) => ({
+                            messageIds: [info.messageId],
+                          }));
+  }
   private getExtensions_() {
     this.browserProxy_!.getExtensions().then(extensions => {
       this.extensions_ = extensions;
@@ -342,6 +365,15 @@
   }
 
   /**
+   * @return Whether there are profile reporting info to show with new format.
+   */
+  private showProfileReportingInfo_(): boolean {
+    return !!this.profileReportingInfo_ &&
+        this.profileReportingInfo_.length > 0;
+  }
+
+
+  /**
    * @return Whether there are extension reporting info to show.
    */
   private showExtensionReportingInfo_(): boolean {
diff --git a/chrome/browser/resources/password_manager/passkeys_browser_proxy.ts b/chrome/browser/resources/password_manager/passkeys_browser_proxy.ts
index 7182975..e29b8faa 100644
--- a/chrome/browser/resources/password_manager/passkeys_browser_proxy.ts
+++ b/chrome/browser/resources/password_manager/passkeys_browser_proxy.ts
@@ -24,7 +24,7 @@
   }
 
   managePasskeys() {
-    return chrome.send('passkeysManagePasskeys');
+    chrome.send('passkeysManagePasskeys');
   }
 
   static getInstance(): PasskeysBrowserProxy {
diff --git a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.ts b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.ts
index b242ae4..1d64193 100644
--- a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.ts
+++ b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_browser_proxy.ts
@@ -77,8 +77,7 @@
   }
 
   restartCounters(isBasic: boolean, timePeriod: number) {
-    return chrome.send('restartClearBrowsingDataCounters',
-                       [isBasic, timePeriod]);
+    chrome.send('restartClearBrowsingDataCounters', [isBasic, timePeriod]);
   }
 
   static getInstance(): ClearBrowsingDataBrowserProxy {
diff --git a/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.ts b/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.ts
index 242b8b8..f8727834 100644
--- a/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.ts
+++ b/chrome/browser/resources/settings/privacy_page/security_keys_browser_proxy.ts
@@ -344,7 +344,7 @@
   }
 
   close() {
-    return chrome.send('securityKeyPINClose');
+    chrome.send('securityKeyPINClose');
   }
 
   static getInstance(): SecurityKeysPinBrowserProxy {
@@ -386,7 +386,7 @@
   }
 
   close() {
-    return chrome.send('securityKeyCredentialManagementClose');
+    chrome.send('securityKeyCredentialManagementClose');
   }
 
   static getInstance(): SecurityKeysCredentialBrowserProxy {
@@ -414,7 +414,7 @@
   }
 
   close() {
-    return chrome.send('securityKeyResetClose');
+    chrome.send('securityKeyResetClose');
   }
 
   static getInstance(): SecurityKeysResetBrowserProxy {
@@ -452,7 +452,7 @@
   }
 
   cancelEnrollment() {
-    return chrome.send('securityKeyBioEnrollCancel');
+    chrome.send('securityKeyBioEnrollCancel');
   }
 
   deleteEnrollment(id: string) {
@@ -464,7 +464,7 @@
   }
 
   close() {
-    return chrome.send('securityKeyBioEnrollClose');
+    chrome.send('securityKeyBioEnrollClose');
   }
 
   static getInstance(): SecurityKeysBioEnrollProxy {
diff --git a/chrome/browser/resources/settings_shared/people_page/sync_browser_proxy.ts b/chrome/browser/resources/settings_shared/people_page/sync_browser_proxy.ts
index ad312f8..2f41fb0 100644
--- a/chrome/browser/resources/settings_shared/people_page/sync_browser_proxy.ts
+++ b/chrome/browser/resources/settings_shared/people_page/sync_browser_proxy.ts
@@ -331,15 +331,15 @@
 
   // <if expr="chromeos_ash">
   attemptUserExit() {
-    return chrome.send('AttemptUserExit');
+    chrome.send('AttemptUserExit');
   }
 
   turnOnSync() {
-    return chrome.send('TurnOnSync');
+    chrome.send('TurnOnSync');
   }
 
   turnOffSync() {
-    return chrome.send('TurnOffSync');
+    chrome.send('TurnOffSync');
   }
   // </if>
 
diff --git a/chrome/browser/resources/side_panel/history_clusters/BUILD.gn b/chrome/browser/resources/side_panel/history_clusters/BUILD.gn
index 8dad0a79..2475ea6 100644
--- a/chrome/browser/resources/side_panel/history_clusters/BUILD.gn
+++ b/chrome/browser/resources/side_panel/history_clusters/BUILD.gn
@@ -11,11 +11,15 @@
 
   static_files = [ "history_clusters.html" ]
 
-  web_component_files = [ "app.ts" ]
+  non_web_component_files = [
+    "app.ts",
+    "app.html.ts",
+  ]
+  css_files = [ "app.css" ]
 
   ts_deps = [
     "../shared:build_ts",
-    "//third_party/polymer/v3_0:library",
+    "//third_party/lit/v3_0:build_ts",
     "//ui/webui/resources/cr_components/color_change_listener:build_ts",
     "//ui/webui/resources/cr_components/history_clusters:build_ts",
     "//ui/webui/resources/cr_elements:build_ts",
diff --git a/chrome/browser/resources/side_panel/history_clusters/app.css b/chrome/browser/resources/side_panel/history_clusters/app.css
new file mode 100644
index 0000000..f07a1e4
--- /dev/null
+++ b/chrome/browser/resources/side_panel/history_clusters/app.css
@@ -0,0 +1,28 @@
+/* Copyright 2024 The Chromium Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+/* #css_wrapper_metadata_start
+ * #type=style-lit
+ * #import=chrome://history-clusters-side-panel.top-chrome/shared/sp_shared_style_lit.css.js
+ * #scheme=relative
+ * #include=sp-shared-style-lit
+ * #css_wrapper_metadata_end */
+
+:host {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+#searchbox {
+  margin: var(--sp-body-padding);
+  flex-shrink: 0;
+  width: auto;
+}
+
+#historyClusters {
+  flex: 1;
+  margin-inline-start: var(--sp-body-padding);
+  padding-bottom: var(--sp-body-padding);
+}
diff --git a/chrome/browser/resources/side_panel/history_clusters/app.html b/chrome/browser/resources/side_panel/history_clusters/app.html
deleted file mode 100644
index ddf30c0..0000000
--- a/chrome/browser/resources/side_panel/history_clusters/app.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<style include="sp-shared-style">
-  :host {
-    display: flex;
-    flex-direction: column;
-    height: 100%;
-  }
-
-  #searchbox {
-    margin: var(--sp-body-padding);
-    flex-shrink: 0;
-    width: auto;
-  }
-
-  #historyClusters {
-    flex: 1;
-    margin-inline-start: var(--sp-body-padding);
-    padding-bottom: var(--sp-body-padding);
-  }
-</style>
-<cr-toolbar-search-field id="searchbox" on-search-changed="onSearchChanged_"
-                         label="$i18n{historyClustersSearchPrompt}"
-                         clear-label="$i18n{clearSearch}"
-                         on-contextmenu="onContextMenu_">
-</cr-toolbar-search-field>
-<history-clusters id="historyClusters"
-    query="[[query]]"
-    path="journeys"
-    on-query-changed-by-user="onQueryChangedByUser_"
-    class="sp-scroller sp-scroller-bottom-of-page"
-    scroll-target="[[scrollTarget_]]">
-</history-clusters>
diff --git a/chrome/browser/resources/side_panel/history_clusters/app.html.ts b/chrome/browser/resources/side_panel/history_clusters/app.html.ts
new file mode 100644
index 0000000..0ea22bd
--- /dev/null
+++ b/chrome/browser/resources/side_panel/history_clusters/app.html.ts
@@ -0,0 +1,25 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {html} from '//resources/lit/v3_0/lit.rollup.js';
+
+import type {HistoryClustersAppElement} from './app.js';
+
+export function getHtml(this: HistoryClustersAppElement) {
+  return html`<!--_html_template_start_-->
+<cr-toolbar-search-field id="searchbox"
+    @search-changed="${this.onSearchChanged_}"
+    label="$i18n{historyClustersSearchPrompt}"
+    clear-label="$i18n{clearSearch}"
+    @contextmenu="${this.onContextMenu_}">
+</cr-toolbar-search-field>
+<history-clusters id="historyClusters"
+    query="${this.query}"
+    path="journeys"
+    @query-changed-by-user="${this.onQueryChangedByUser_}"
+    class="sp-scroller sp-scroller-bottom-of-page"
+    .scrollTarget="${this.scrollTarget_}">
+</history-clusters>
+<!--_html_template_end_-->`;
+}
diff --git a/chrome/browser/resources/side_panel/history_clusters/app.ts b/chrome/browser/resources/side_panel/history_clusters/app.ts
index f2f0b14..6efe1ed 100644
--- a/chrome/browser/resources/side_panel/history_clusters/app.ts
+++ b/chrome/browser/resources/side_panel/history_clusters/app.ts
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 import '../strings.m.js';
-import 'chrome://history-clusters-side-panel.top-chrome/shared/sp_shared_style.css.js';
 import 'chrome://resources/cr_components/history_clusters/browser_proxy.js';
 import 'chrome://resources/cr_components/history_clusters/clusters.js';
 import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
@@ -11,9 +10,10 @@
 import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
 import {BrowserProxyImpl} from 'chrome://resources/cr_components/history_clusters/browser_proxy.js';
 import type {CrToolbarSearchFieldElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
-import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
 
-import {getTemplate} from './app.html.js';
+import {getCss} from './app.css.js';
+import {getHtml} from './app.html.js';
 
 export interface HistoryClustersAppElement {
   $: {
@@ -22,26 +22,27 @@
   };
 }
 
-export class HistoryClustersAppElement extends PolymerElement {
+export class HistoryClustersAppElement extends CrLitElement {
   static get is() {
     return 'history-clusters-app';
   }
 
-  static get template() {
-    return getTemplate();
+  static override get styles() {
+    return getCss();
   }
 
-  static get properties() {
+  override render() {
+    return getHtml.bind(this)();
+  }
+
+  static override get properties() {
     return {
       /**
        * The current query for which related clusters are requested and shown.
        */
-      query: {
-        type: String,
-        value: '',
-      },
+      query: {type: String},
 
-      scrollContainer: HTMLElement,
+      scrollTarget_: {type: Object},
     };
   }
 
@@ -54,14 +55,14 @@
   // Properties
   //============================================================================
 
-  query: string;
-  private scrollTarget_: HTMLElement;
+  query: string = '';
+  protected scrollTarget_?: HTMLElement;
 
   //============================================================================
   // Event Handlers
   //============================================================================
 
-  private onContextMenu_(event: MouseEvent) {
+  protected onContextMenu_(event: MouseEvent) {
     BrowserProxyImpl.getInstance().handler.showContextMenuForSearchbox(
         this.query, {x: event.clientX, y: event.clientY});
   }
@@ -86,7 +87,7 @@
   /**
    * Called when the value of the search field changes.
    */
-  private onSearchChanged_(event: CustomEvent<string>) {
+  protected onSearchChanged_(event: CustomEvent<string>) {
     // Update the query based on the value of the search field, if necessary.
     this.query = event.detail;
   }
@@ -94,7 +95,7 @@
   /**
    * Called when the browser handler forces us to change our query.
    */
-  private onQueryChangedByUser_(event: CustomEvent<string>) {
+  protected onQueryChangedByUser_(event: CustomEvent<string>) {
     // This will in turn fire `onSearchChanged_()`.
     if (this.$.searchbox) {
       this.$.searchbox.setValue(event.detail);
diff --git a/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.ts b/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.ts
index 7a57cb2..a241f2b 100644
--- a/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.ts
+++ b/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.ts
@@ -24,7 +24,7 @@
 
 export class NtpBackgroundProxyImpl implements NtpBackgroundProxy {
   clearBackground() {
-    return chrome.send('clearBackground');
+    chrome.send('clearBackground');
   }
 
   getBackgrounds() {
diff --git a/chrome/browser/safe_browsing/android/safe_browsing_bridge.cc b/chrome/browser/safe_browsing/android/safe_browsing_bridge.cc
index b02d42ad..788bbbd 100644
--- a/chrome/browser/safe_browsing/android/safe_browsing_bridge.cc
+++ b/chrome/browser/safe_browsing/android/safe_browsing_bridge.cc
@@ -8,7 +8,6 @@
 // NOTE: This target is transitively depended on by //chrome/browser and thus
 // can't depend on it.
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
 #include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"  // nogncheck
@@ -23,7 +22,7 @@
 namespace {
 
 PrefService* GetPrefService(const base::android::JavaRef<jobject>& j_profile) {
-  return ProfileAndroid::FromProfileAndroid(j_profile)->GetPrefs();
+  return Profile::FromJavaObject(j_profile)->GetPrefs();
 }
 
 }  // namespace
@@ -86,7 +85,7 @@
 static jboolean JNI_SafeBrowsingBridge_IsUnderAdvancedProtection(
     JNIEnv* env,
     const JavaParamRef<jobject>& j_profile) {
-  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
+  Profile* profile = Profile::FromJavaObject(j_profile);
   return profile &&
          safe_browsing::AdvancedProtectionStatusManagerFactory::GetForProfile(
              profile)
diff --git a/chrome/browser/search_resumption/start_suggest_service_factory.cc b/chrome/browser/search_resumption/start_suggest_service_factory.cc
index 01ca533b..859c1a1 100644
--- a/chrome/browser/search_resumption/start_suggest_service_factory.cc
+++ b/chrome/browser/search_resumption/start_suggest_service_factory.cc
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <utility>
-
 #include "start_suggest_service_factory.h"
 
+#include <utility>
+
 #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/common/webui_url_constants.h"
 #include "components/search/start_suggest_service.h"
diff --git a/chrome/browser/share/share_history.cc b/chrome/browser/share/share_history.cc
index 282ad48e..83c2e43 100644
--- a/chrome/browser/share/share_history.cc
+++ b/chrome/browser/share/share_history.cc
@@ -18,7 +18,7 @@
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/jni_string.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 
 // Must come after other includes, because FromJniType() uses Profile.
 #include "chrome/browser/share/jni_headers/ShareHistoryBridge_jni.h"
diff --git a/chrome/browser/shortcuts/OWNERS b/chrome/browser/shortcuts/OWNERS
index fead5da..5eb3421b 100644
--- a/chrome/browser/shortcuts/OWNERS
+++ b/chrome/browser/shortcuts/OWNERS
@@ -2,4 +2,4 @@
 dmurph@chromium.org
 mek@chromium.org
 
-per-file *_windows=davidbienvenu@chromium.org
\ No newline at end of file
+per-file *_win*=davidbienvenu@chromium.org
\ No newline at end of file
diff --git a/chrome/browser/shortcuts/shortcut_creator_win.cc b/chrome/browser/shortcuts/shortcut_creator_win.cc
index 01427d6..f479fe9 100644
--- a/chrome/browser/shortcuts/shortcut_creator_win.cc
+++ b/chrome/browser/shortcuts/shortcut_creator_win.cc
@@ -87,8 +87,10 @@
   bool res =
       CreateOrUpdateShortcutLink(shortcut_path, target_and_args_properties,
                                  base::win::ShortcutOperation::kCreateAlways);
-  std::move(complete).Run(res ? ShortcutCreatorResult::kSuccess
-                              : ShortcutCreatorResult::kError);
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(complete), res ? ShortcutCreatorResult::kSuccess
+                                              : ShortcutCreatorResult::kError));
 }
 
 scoped_refptr<base::SequencedTaskRunner> GetShortcutsTaskRunner() {
diff --git a/chrome/browser/shortcuts/shortcut_creator_win_unittest.cc b/chrome/browser/shortcuts/shortcut_creator_win_unittest.cc
index 95e39a6a..589c1a0 100644
--- a/chrome/browser/shortcuts/shortcut_creator_win_unittest.cc
+++ b/chrome/browser/shortcuts/shortcut_creator_win_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_path_override.h"
+#include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "base/test/test_shortcut_win.h"
 #include "base/win/scoped_com_initializer.h"
@@ -68,6 +69,7 @@
   base::ScopedTempDir default_profile_path_;
   base::ScopedPathOverride desktop_override_{base::DIR_USER_DESKTOP};
   base::win::ScopedCOMInitializer com_initializer_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 };
 
 void ShortcutCreatorWinTest::VerifyShortcut() const {
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.cc b/chrome/browser/signin/dice_web_signin_interceptor.cc
index 0dc075e..3376a91d 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor.cc
@@ -48,11 +48,13 @@
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
 #include "chrome/browser/ui/profiles/profile_colors_util.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/themes/autogenerated_theme_util.h"
+#include "components/feature_engagement/public/feature_constants.h"
 #include "components/password_manager/core/browser/password_manager.h"
 #include "components/password_manager/core/common/password_manager_ui.h"
 #include "components/policy/core/browser/signin/profile_separation_policies.h"
@@ -1035,6 +1037,17 @@
   return processed_result;
 }
 
+void DiceWebSigninInterceptor::
+    MaybeShowExplicitBrowserSigninPreferenceRememberedIPH(
+        const AccountInfo& account_info) {
+  Browser* browser = chrome::FindBrowserWithProfile(profile_);
+  user_education::FeaturePromoParams params(
+      feature_engagement::kIPHExplicitBrowserSigninPreferenceRememberedFeature,
+      account_info.gaia);
+  params.title_params = base::UTF8ToUTF16(account_info.given_name);
+  browser->window()->MaybeShowFeaturePromo(std::move(params));
+}
+
 void DiceWebSigninInterceptor::OnChromeSigninChoice(
     const AccountInfo& account_info,
     SigninInterceptionResult result) {
@@ -1043,7 +1056,7 @@
 
   switch (processed_result) {
     case SigninInterceptionResult::kIgnored:
-      // Can happen if the browser isclosed while the bubble is still opened.
+      // Can happen if the browser is closed while the bubble is still opened.
     case SigninInterceptionResult::kNotDisplayed:
       // Can happen if the web contents is destroyed between the time the bubble
       // was requested to be displayed and actually being displayed.
@@ -1067,6 +1080,7 @@
       signin_metrics::LogSignInStarted(access_point);
       identity_manager_->GetPrimaryAccountMutator()->SetPrimaryAccount(
           account_info.account_id, signin::ConsentLevel::kSignin, access_point);
+      MaybeShowExplicitBrowserSigninPreferenceRememberedIPH(account_info);
   }
 
   // In all cases we want to close the bubble after the choice is taken.
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.h b/chrome/browser/signin/dice_web_signin_interceptor.h
index b19e52ef..8a22434 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.h
+++ b/chrome/browser/signin/dice_web_signin_interceptor.h
@@ -235,6 +235,11 @@
       const WebSigninInterceptor::Delegate::BubbleParameters& bubble_parameters,
       base::OnceCallback<void(SigninInterceptionResult)> callback);
 
+  // Attempts showing the In-Product-Help for remembering the explicit browser
+  // sign-in preference.
+  void MaybeShowExplicitBrowserSigninPreferenceRememberedIPH(
+      const AccountInfo& account_info);
+
   // Ensure that we are observing changes in extended account info. Idempotent.
   void EnsureObservingExtendedAccountInfo();
 
diff --git a/chrome/browser/supervised_user/android/website_parent_approval.cc b/chrome/browser/supervised_user/android/website_parent_approval.cc
index a90fc87..bfbf7cf 100644
--- a/chrome/browser/supervised_user/android/website_parent_approval.cc
+++ b/chrome/browser/supervised_user/android/website_parent_approval.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/supervised_user/android/website_parent_approval.h"
 
 #include <jni.h>
+
 #include <memory>
 
 #include "base/android/callback_android.h"
@@ -15,7 +16,6 @@
 #include "base/no_destructor.h"
 #include "chrome/browser/favicon/large_icon_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/supervised_user/android/favicon_fetcher.h"
 #include "chrome/browser/supervised_user/website_parent_approval_jni_headers/WebsiteParentApproval_jni.h"
@@ -67,8 +67,7 @@
   JNIEnv* env = base::android::AttachCurrentThread();
   Java_WebsiteParentApproval_requestLocalApproval(
       env, window_android->GetJavaObject(),
-      url::GURLAndroid::FromNativeGURL(env, url),
-      ProfileAndroid::FromProfile(&profile)->GetJavaObject());
+      url::GURLAndroid::FromNativeGURL(env, url), profile.GetJavaObject());
 }
 
 void JNI_WebsiteParentApproval_OnCompletion(JNIEnv* env,
diff --git a/chrome/browser/tab_group_sync/feature_utils.cc b/chrome/browser/tab_group_sync/feature_utils.cc
index 23351de..e5aa1a0 100644
--- a/chrome/browser/tab_group_sync/feature_utils.cc
+++ b/chrome/browser/tab_group_sync/feature_utils.cc
@@ -11,7 +11,6 @@
 #include "base/android/jni_android.h"
 #include "base/android/scoped_java_ref.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/tab_group_sync/utils_jni_headers/TabGroupSyncFeatures_jni.h"
 #include "components/saved_tab_groups/features.h"
 #include "components/saved_tab_groups/pref_names.h"
diff --git a/chrome/browser/tab_resumption/visited_url_ranking_backend.cc b/chrome/browser/tab_resumption/visited_url_ranking_backend.cc
index ae75979..25772902 100644
--- a/chrome/browser/tab_resumption/visited_url_ranking_backend.cc
+++ b/chrome/browser/tab_resumption/visited_url_ranking_backend.cc
@@ -39,7 +39,11 @@
 using visited_url_ranking::URLVisitAggregate;
 using visited_url_ranking::VisitedURLRankingService;
 
+// FetchOptions::CreateDefaultFetchOptionsForTabResumption() specifies data
+// sources that are currently unavailable. This function returns a simplified
+// FetchOptions instance.
 FetchOptions CreateFetchOptionsForTabResumption(base::Time current_time) {
+  // TODO(crbug.com/337858147): Incorporate Fetcher::kHistory when ready.
   return FetchOptions(
       {
           {Fetcher::kSession, FetchOptions::kOriginSources},
@@ -85,14 +89,9 @@
       return;
     }
 
-    PassResults(std::move(aggregates));
-
-    // TODO(crbug.com/337858147): Uncomment to use ranking, once implemented.
-
-    // ranking_service_->RankVisitAggregates(
-    //     config_, std::move(aggregates),
-    //     base::BindOnce(&FetchAndRankFlow::OnRanked,
-    //     base::RetainedRef(this)));
+    ranking_service_->RankURLVisitAggregates(
+        config_, std::move(aggregates),
+        base::BindOnce(&FetchAndRankFlow::OnRanked, base::RetainedRef(this)));
   }
 
   // Continuing after OnFetched()'s call to RankVisitAggregates().
@@ -115,19 +114,23 @@
       if (aggregate.fetcher_data_map.empty()) {
         continue;
       }
-      const URLVisitAggregate::TabData& tab_data =
-          std::get<URLVisitAggregate::TabData>(
-              aggregate.fetcher_data_map.begin()->second);
-      Java_VisitedUrlRankingBackend_addSuggestionEntry(
-          env_,
-          base::android::ConvertUTF8ToJavaString(
-              env_, tab_data.last_active_tab.session_name.value_or("?")),
-          url::GURLAndroid::FromNativeGURL(env_,
-                                           tab_data.last_active_tab.visit.url),
-          base::android::ConvertUTF16ToJavaString(
-              env_, tab_data.last_active_tab.visit.title),
-          tab_data.last_active.InMillisecondsSinceUnixEpoch(),
-          tab_data.last_active_tab.id, j_suggestions_);
+      const URLVisitAggregate::TabData* tab_data =
+          std::get_if<URLVisitAggregate::TabData>(
+              &(aggregate.fetcher_data_map.begin()->second));
+      if (tab_data) {
+        Java_VisitedUrlRankingBackend_addSuggestionEntry(
+            env_,
+            base::android::ConvertUTF8ToJavaString(
+                env_, tab_data->last_active_tab.session_name.value_or("?")),
+            url::GURLAndroid::FromNativeGURL(
+                env_, tab_data->last_active_tab.visit.url),
+            base::android::ConvertUTF16ToJavaString(
+                env_, tab_data->last_active_tab.visit.title),
+            tab_data->last_active.InMillisecondsSinceUnixEpoch(),
+            tab_data->last_active_tab.id, j_suggestions_);
+      }
+
+      // TODO(crbug.com/337858147): Handle URLVisitAggregate::HistoryData case.
     }
 
     Java_VisitedUrlRankingBackend_onSuggestions(env_, j_suggestions_,
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelUtils.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelUtils.java
index 54f6e54..34ad79d 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelUtils.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelUtils.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.tabmodel;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
@@ -14,6 +15,9 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A set of convenience methods used for interacting with {@link TabList}s and {@link TabModel}s.
  */
@@ -23,7 +27,7 @@
     /**
      * @param model The {@link TabModel} to act on.
      * @param index The index of the {@link Tab} to close.
-     * @return      {@code true} if the {@link Tab} was found.
+     * @return {@code true} if the {@link Tab} was found.
      */
     public static boolean closeTabByIndex(TabModel model, int index) {
         Tab tab = model.getTabAt(index);
@@ -259,4 +263,15 @@
 
         return selector.getTabModelFilterProvider().getTabModelFilter(tab.isIncognito());
     }
+
+    /** Converts a {@link TabList} to a {@link List<Tab>}. A null input returns an empty list. */
+    public static @Nullable List<Tab> convertTabListToListOfTabs(@Nullable TabList tabList) {
+        ArrayList<Tab> list = new ArrayList<>();
+        if (tabList == null) return list;
+
+        for (int i = 0; i < tabList.getCount(); i++) {
+            list.add(tabList.getTabAt(i));
+        }
+        return list;
+    }
 }
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
index d004cfed..30a6920 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
@@ -66,7 +66,6 @@
 import org.chromium.chrome.browser.touch_to_fill.common.BottomSheetFocusHelper;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodMediator.TouchToFillCreditCardOutcome;
 import org.chromium.components.autofill.AutofillFeatures;
-import org.chromium.components.autofill.IbanRecordType;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.StateChangeReason;
@@ -141,7 +140,6 @@
                     /* guid= */ "000000111111",
                     /* label= */ "CH56 **** **** **** *800 9",
                     /* nickname= */ "My brother's IBAN",
-                    /* recordType= */ IbanRecordType.LOCAL_IBAN,
                     /* value= */ "CH5604835012345678009");
 
     private static final Iban LOCAL_IBAN_NO_NICKNAME =
@@ -149,7 +147,6 @@
                     /* guid= */ "000000222222",
                     /* label= */ "FR76 **** **** **** **** ***0 189",
                     /* nickname= */ "",
-                    /* recordType= */ IbanRecordType.LOCAL_IBAN,
                     /* value= */ "FR7630006000011234567890189");
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
index e30423c2..1856744c 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodRenderTest.java
@@ -42,7 +42,6 @@
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
-import org.chromium.components.autofill.IbanRecordType;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
 import org.chromium.ui.test.util.RenderTestRule.Component;
@@ -152,7 +151,6 @@
                     /* guid= */ "000000111111",
                     /* label= */ "CH56 •••• •••• •••• •800 9",
                     /* nickname= */ "My brother's IBAN",
-                    /* recordType= */ IbanRecordType.LOCAL_IBAN,
                     /* value= */ "CH5604835012345678009");
 
     private static final Iban LOCAL_IBAN_NO_NICKNAME =
@@ -160,7 +158,6 @@
                     /* guid= */ "000000222222",
                     /* label= */ "FR76 •••• •••• •••• •••• •••0 189",
                     /* nickname= */ "",
-                    /* recordType= */ IbanRecordType.LOCAL_IBAN,
                     /* value= */ "FR7630006000011234567890189");
 
     private BottomSheetController mBottomSheetController;
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
index 1479bb30..1da7161 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
@@ -57,7 +57,6 @@
 import org.chromium.chrome.browser.touch_to_fill.common.FillableItemCollectionInfo;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.components.autofill.IbanRecordType;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController.SheetState;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
@@ -123,7 +122,6 @@
                     /* guid= */ "000000111111",
                     /* label= */ "CH56 **** **** **** *800 9",
                     /* nickname= */ "My brother's IBAN",
-                    /* recordType= */ IbanRecordType.LOCAL_IBAN,
                     /* value= */ "CH5604835012345678009");
 
     @Rule
diff --git a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.cc b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.cc
index 28fff45..c85d9cc3 100644
--- a/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.cc
+++ b/chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_impl.cc
@@ -7,7 +7,6 @@
 #include "base/android/scoped_java_ref.h"
 #include "chrome/browser/autofill/android/personal_data_manager_android.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/touch_to_fill/autofill/android/internal/jni/TouchToFillPaymentMethodViewBridge_jni.h"
 #include "chrome/browser/touch_to_fill/autofill/android/touch_to_fill_payment_method_view_controller.h"
 #include "components/autofill/core/common/autofill_features.h"
@@ -46,8 +45,7 @@
 
   java_object_.Reset(Java_TouchToFillPaymentMethodViewBridge_create(
       env, java_controller,
-      ProfileAndroid::FromProfile(
-          Profile::FromBrowserContext(web_contents_->GetBrowserContext()))
+      Profile::FromBrowserContext(web_contents_->GetBrowserContext())
           ->GetJavaObject(),
       web_contents_->GetTopLevelNativeWindow()->GetJavaObject()));
   if (!java_object_)
diff --git a/chrome/browser/touch_to_fill/password_manager/android/touch_to_fill_view_impl.cc b/chrome/browser/touch_to_fill/password_manager/android/touch_to_fill_view_impl.cc
index f613207..9403695 100644
--- a/chrome/browser/touch_to_fill/password_manager/android/touch_to_fill_view_impl.cc
+++ b/chrome/browser/touch_to_fill/password_manager/android/touch_to_fill_view_impl.cc
@@ -14,7 +14,6 @@
 #include "base/android/jni_string.h"
 #include "base/time/time.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/touch_to_fill/password_manager/android/internal/jni/TouchToFillBridge_jni.h"
 #include "chrome/browser/touch_to_fill/password_manager/android/jni_headers/Credential_jni.h"
 #include "chrome/browser/touch_to_fill/password_manager/android/jni_headers/WebauthnCredential_jni.h"
@@ -200,7 +199,7 @@
   }
   java_object_internal_ = Java_TouchToFillBridge_create(
       AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
-      ProfileAndroid::FromProfile(controller_->GetProfile())->GetJavaObject(),
+      controller_->GetProfile()->GetJavaObject(),
       controller_->GetNativeView()->GetWindowAndroid()->GetJavaObject());
   return !!java_object_internal_;
 }
diff --git a/chrome/browser/translate/android/translate_bridge.cc b/chrome/browser/translate/android/translate_bridge.cc
index 037cb77..f2f467a 100644
--- a/chrome/browser/translate/android/translate_bridge.cc
+++ b/chrome/browser/translate/android/translate_bridge.cc
@@ -14,7 +14,6 @@
 #include "chrome/browser/language/android/jni_headers/TranslationObserver_jni.h"
 #include "chrome/browser/language/language_model_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/translate/translate_service.h"
 #include "components/language/core/browser/language_model.h"
@@ -42,7 +41,7 @@
 namespace {
 
 PrefService* GetPrefService(const base::android::JavaRef<jobject>& j_profile) {
-  return ProfileAndroid::FromProfileAndroid(j_profile)->GetPrefs();
+  return Profile::FromJavaObject(j_profile)->GetPrefs();
 }
 
 class TranslationObserver
@@ -147,7 +146,7 @@
 static base::android::ScopedJavaLocalRef<jstring>
 JNI_TranslateBridge_GetTargetLanguage(JNIEnv* env,
                                       const JavaParamRef<jobject>& j_profile) {
-  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
+  Profile* profile = Profile::FromJavaObject(j_profile);
   language::LanguageModel* language_model =
       LanguageModelManagerFactory::GetForBrowserContext(profile)
           ->GetPrimaryModel();
diff --git a/chrome/browser/ui/android/autofill/card_unmask_prompt_view_android.cc b/chrome/browser/ui/android/autofill/card_unmask_prompt_view_android.cc
index 5684f9c5..2d0b095d 100644
--- a/chrome/browser/ui/android/autofill/card_unmask_prompt_view_android.cc
+++ b/chrome/browser/ui/android/autofill/card_unmask_prompt_view_android.cc
@@ -7,7 +7,6 @@
 #include "chrome/android/chrome_jni_headers/CardUnmaskBridge_jni.h"
 #include "chrome/browser/android/resource_mapper.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/ui/autofill/payments/create_card_unmask_prompt_view.h"
 #include "components/autofill/core/browser/ui/autofill_resource_utils.h"
 #include "components/autofill/core/browser/ui/payments/card_unmask_prompt_controller.h"
@@ -168,9 +167,7 @@
 
   return java_object_internal_ = Java_CardUnmaskBridge_create(
              env, reinterpret_cast<intptr_t>(this),
-             ProfileAndroid::FromProfile(
-                 Profile::FromBrowserContext(
-                     web_contents_->GetBrowserContext()))
+             Profile::FromBrowserContext(web_contents_->GetBrowserContext())
                  ->GetJavaObject(),
              dialog_title, instructions,
              ResourceMapper::MapToJavaDrawableId(
diff --git a/chrome/browser/ui/android/autofill/save_update_address_profile_prompt_view_android.cc b/chrome/browser/ui/android/autofill/save_update_address_profile_prompt_view_android.cc
index c7d4154..7b95c9a3 100644
--- a/chrome/browser/ui/android/autofill/save_update_address_profile_prompt_view_android.cc
+++ b/chrome/browser/ui/android/autofill/save_update_address_profile_prompt_view_android.cc
@@ -2,17 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/ui/android/autofill/save_update_address_profile_prompt_view_android.h"
+
 #include <memory>
 #include <utility>
 
-#include "chrome/browser/ui/android/autofill/save_update_address_profile_prompt_view_android.h"
-
 #include "base/android/jni_string.h"
 #include "chrome/android/chrome_jni_headers/SaveUpdateAddressProfilePrompt_jni.h"
 #include "chrome/browser/autofill/android/personal_data_manager_android.h"
 #include "chrome/browser/autofill/android/save_update_address_profile_prompt_controller.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "content/public/browser/web_contents.h"
@@ -56,10 +56,6 @@
 
   Profile* browser_profile =
       Profile::FromBrowserContext(web_contents_->GetBrowserContext());
-  ProfileAndroid* browser_profile_android =
-      ProfileAndroid::FromProfile(browser_profile);
-  if (!browser_profile_android)
-    return false;
 
   JNIEnv* env = base::android::AttachCurrentThread();
   base::android::ScopedJavaLocalRef<jobject> java_autofill_profile =
@@ -67,8 +63,8 @@
           g_browser_process->GetApplicationLocale());
   java_object_.Reset(Java_SaveUpdateAddressProfilePrompt_create(
       env, web_contents_->GetTopLevelNativeWindow()->GetJavaObject(),
-      java_controller, browser_profile_android->GetJavaObject(),
-      java_autofill_profile, static_cast<jboolean>(is_update),
+      java_controller, browser_profile->GetJavaObject(), java_autofill_profile,
+      static_cast<jboolean>(is_update),
       static_cast<jboolean>(is_migration_to_account)));
   if (!java_object_)
     return false;
diff --git a/chrome/browser/ui/android/device_dialog/chrome_bluetooth_chooser_android_delegate.cc b/chrome/browser/ui/android/device_dialog/chrome_bluetooth_chooser_android_delegate.cc
index 1e97cbc..8cb5479 100644
--- a/chrome/browser/ui/android/device_dialog/chrome_bluetooth_chooser_android_delegate.cc
+++ b/chrome/browser/ui/android/device_dialog/chrome_bluetooth_chooser_android_delegate.cc
@@ -7,14 +7,13 @@
 #include "base/android/jni_android.h"
 #include "chrome/android/chrome_jni_headers/ChromeBluetoothChooserAndroidDelegate_jni.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
 
 ChromeBluetoothChooserAndroidDelegate::ChromeBluetoothChooserAndroidDelegate(
     Profile* profile) {
   JNIEnv* env = base::android::AttachCurrentThread();
   java_delegate_.Reset(Java_ChromeBluetoothChooserAndroidDelegate_Constructor(
-      env, ProfileAndroid::FromProfile(profile)->GetJavaObject()));
+      env, profile->GetJavaObject()));
 }
 
 ChromeBluetoothChooserAndroidDelegate::
diff --git a/chrome/browser/ui/android/device_dialog/chrome_bluetooth_scanning_prompt_android_delegate.cc b/chrome/browser/ui/android/device_dialog/chrome_bluetooth_scanning_prompt_android_delegate.cc
index 7065235..f9a6eb6 100644
--- a/chrome/browser/ui/android/device_dialog/chrome_bluetooth_scanning_prompt_android_delegate.cc
+++ b/chrome/browser/ui/android/device_dialog/chrome_bluetooth_scanning_prompt_android_delegate.cc
@@ -7,7 +7,6 @@
 #include "base/android/jni_android.h"
 #include "chrome/android/chrome_jni_headers/ChromeBluetoothScanningPromptAndroidDelegate_jni.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
 
 ChromeBluetoothScanningPromptAndroidDelegate::
@@ -15,7 +14,7 @@
   JNIEnv* env = base::android::AttachCurrentThread();
   java_delegate_.Reset(
       Java_ChromeBluetoothScanningPromptAndroidDelegate_Constructor(
-          env, ProfileAndroid::FromProfile(profile)->GetJavaObject()));
+          env, profile->GetJavaObject()));
 }
 
 ChromeBluetoothScanningPromptAndroidDelegate::
diff --git a/chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.cc b/chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.cc
index 386cf31..2a1446c1 100644
--- a/chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.cc
+++ b/chrome/browser/ui/android/device_dialog/usb_chooser_dialog_android.cc
@@ -15,7 +15,6 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/android/chrome_jni_headers/UsbChooserDialog_jni.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/ssl/security_state_tab_helper.h"
 #include "chrome/common/url_constants.h"
 #include "components/permissions/permission_util.h"
@@ -89,7 +88,7 @@
   DCHECK(profile);
 
   base::android::ScopedJavaLocalRef<jobject> j_profile_android =
-      ProfileAndroid::FromProfile(profile)->GetJavaObject();
+      profile->GetJavaObject();
   DCHECK(!j_profile_android.is_null());
 
   auto dialog = std::make_unique<UsbChooserDialogAndroid>(std::move(controller),
diff --git a/chrome/browser/ui/android/hats/survey_client_android.cc b/chrome/browser/ui/android/hats/survey_client_android.cc
index 52b9c4c..8e8c1c41 100644
--- a/chrome/browser/ui/android/hats/survey_client_android.cc
+++ b/chrome/browser/ui/android/hats/survey_client_android.cc
@@ -12,7 +12,6 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/ranges/algorithm.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/ui/android/hats/internal/jni_headers/SurveyClientBridge_jni.h"
 #include "chrome/browser/ui/android/hats/survey_config_android.h"
 #include "ui/android/window_android.h"
@@ -38,8 +37,7 @@
                                        : std::string_view());
   jobj_ = Java_SurveyClientBridge_create(
       env, reinterpret_cast<int64_t>(this), java_trigger,
-      ui_delegate->GetJavaObject(env),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject(),
+      ui_delegate->GetJavaObject(env), profile->GetJavaObject(),
       java_supplied_trigger_id);
 }
 
diff --git a/chrome/browser/ui/android/page_insights/DEPS b/chrome/browser/ui/android/page_insights/DEPS
index c211777..152a5629 100644
--- a/chrome/browser/ui/android/page_insights/DEPS
+++ b/chrome/browser/ui/android/page_insights/DEPS
@@ -1,7 +1,7 @@
 include_rules = [
   "+chrome/browser/browser_controls/android",
   "+chrome/browser/history/web_history_service_factory.h",
-  "+chrome/browser/profiles/profile_android.h",
+  "+chrome/browser/profiles/profile.h",
   "+chrome/browser/tab",
   "+chrome/browser/tabmodel",
   "+components/browser_ui/bottomsheet/android",
diff --git a/chrome/browser/ui/android/passwords/all_passwords_bottom_sheet_view_impl.cc b/chrome/browser/ui/android/passwords/all_passwords_bottom_sheet_view_impl.cc
index f079e47..e029acee 100644
--- a/chrome/browser/ui/android/passwords/all_passwords_bottom_sheet_view_impl.cc
+++ b/chrome/browser/ui/android/passwords/all_passwords_bottom_sheet_view_impl.cc
@@ -9,7 +9,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/android/features/keyboard_accessory/internal/jni/AllPasswordsBottomSheetBridge_jni.h"
 #include "chrome/browser/password_manager/android/all_passwords_bottom_sheet_controller.h"
-#include "chrome/browser/profiles/profile_android.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/passwords/ui_utils.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/affiliations/core/browser/affiliation_utils.h"
@@ -101,8 +101,7 @@
   }
   return java_object_internal_ = Java_AllPasswordsBottomSheetBridge_create(
              AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
-             ProfileAndroid::FromProfile(controller_->GetProfile())
-                 ->GetJavaObject(),
+             controller_->GetProfile()->GetJavaObject(),
              controller_->GetNativeView()->GetWindowAndroid()->GetJavaObject(),
              controller_->GetFrameUrl().spec());
 }
diff --git a/chrome/browser/ui/android/tab_model/tab_model_jni_bridge.cc b/chrome/browser/ui/android/tab_model/tab_model_jni_bridge.cc
index f40e0156..ce26dff 100644
--- a/chrome/browser/ui/android/tab_model/tab_model_jni_bridge.cc
+++ b/chrome/browser/ui/android/tab_model/tab_model_jni_bridge.cc
@@ -14,7 +14,6 @@
 #include "chrome/browser/android/tab_android.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/profiles/profile_android.h"
 #include "chrome/browser/tab_contents/tab_util.h"
 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
 #include "chrome/browser/ui/android/tab_model/tab_model_observer_jni_bridge.h"
@@ -85,8 +84,7 @@
 
   Java_TabModelJniBridge_createTabWithWebContents(
       env, java_object_.get(env), (parent ? parent->GetJavaObject() : nullptr),
-      ProfileAndroid::FromProfile(profile)->GetJavaObject(),
-      web_contents->GetJavaWebContents());
+      profile->GetJavaObject(), web_contents->GetJavaWebContents());
 }
 
 void TabModelJniBridge::HandlePopupNavigation(TabAndroid* parent,
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
index 3ae6d7e..8473914 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediator.java
@@ -492,12 +492,12 @@
     }
 
     /**
-     * Update the background color of the toolbar based on whether it is in the Grid tab switcher
-     * or in the Start surface with either incognito mode or non-incognito mode.
+     * Update the background color of the toolbar based on whether it is in the Grid tab switcher or
+     * in the Start surface with either incognito mode or non-incognito mode.
      */
     private void updateBackgroundColor() {
         @ColorInt int backgroundColor;
-        if (mIsSurfacePolished && isOnHomepage() && !mIsIncognito) {
+        if (isOnHomepage() && !mIsIncognito) {
             backgroundColor =
                     ChromeColors.getSurfaceColor(
                             mContext,
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabStripTransitionCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabStripTransitionCoordinator.java
index 217a16b..f38ca1a 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabStripTransitionCoordinator.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabStripTransitionCoordinator.java
@@ -373,9 +373,9 @@
         // clears out, #onTokenUpdated will route into this method again.
         if (mDeferTransitionTokenHolder.hasTokens()) return;
 
-        // Invalid width will be ignored. This can happen when the mControlContainer is created
-        // hidden after theme changes. See crbug.com/1511599.
-        if (tabStripWidth <= 0) return;
+        // Invalid width / height will be ignored. This can happen when the mControlContainer is
+        // created hidden after theme changes. See crbug.com/1511599.
+        if (tabStripWidth <= 0 || controlContainerView().getHeight() == 0) return;
 
         boolean showTabStrip;
         if (ToolbarFeatures.isTabStripWindowLayoutOptimizationEnabled(/* isTablet= */ true)) {
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabStripTransitionCoordinatorUnitTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabStripTransitionCoordinatorUnitTest.java
index 84a6b07..fd6b761 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabStripTransitionCoordinatorUnitTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabStripTransitionCoordinatorUnitTest.java
@@ -280,6 +280,19 @@
     }
 
     @Test
+    public void hideTabStripBeforeLayout() {
+        // Simulate the control container hasn't been measured yet.
+        doReturn(0).when(mSpyControlContainer).getWidth();
+        doReturn(0).when(mSpyControlContainer).getHeight();
+
+        setDeviceWidthDp(NARROW_WINDOW_WIDTH);
+        Assert.assertEquals(
+                "Height request should be ignored if control container hasn't been measured.",
+                NOTHING_OBSERVED,
+                mObserver.heightRequested);
+    }
+
+    @Test
     @Config(qualifiers = "w320dp")
     public void showTabStrip() {
         settleTransitionDuringInitForNarrowWindow();
@@ -463,6 +476,22 @@
     }
 
     @Test
+    @Config(qualifiers = "w320dp")
+    public void showTabStripBeforeLayout() {
+        settleTransitionDuringInitForNarrowWindow();
+
+        // Simulate the control container hasn't been measured yet.
+        doReturn(0).when(mSpyControlContainer).getWidth();
+        doReturn(0).when(mSpyControlContainer).getHeight();
+
+        setDeviceWidthDp(600);
+        Assert.assertEquals(
+                "Height request should be ignored if control container hasn't been measured.",
+                NOTHING_OBSERVED,
+                mObserver.heightRequested);
+    }
+
+    @Test
     public void configurationChangedDuringDelayedTask() {
         setConfigurationWithNewWidth(NARROW_WINDOW_WIDTH);
         simulateLayoutChange(NARROW_WINDOW_WIDTH);
@@ -639,6 +668,28 @@
     }
 
     @Test
+    public void useDesktopWindowStateProvider_WithouControlContainerLayout() {
+        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
+        // Simulate a rect update that has a smaller width.
+        int newHeight = TEST_TAB_STRIP_HEIGHT + 10;
+        Rect appHeaderRect = new Rect(0, 0, NARROW_WINDOW_WIDTH, newHeight);
+        mAppHeaderState = new AppHeaderState(appHeaderRect, appHeaderRect, true);
+
+        // Set the height as if the first measure pass hasn't happened yet.
+        doReturn(0).when(mSpyControlContainer).getHeight();
+        doReturn(0).when(mSpyControlContainer).getWidth();
+
+        // Create the transition coordinator again with initial value of AppHeaderState.
+        setUpTabStripTransitionCoordinator();
+        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+        Assert.assertEquals(
+                "Height request should be ignored if control container hasn't been measured.",
+                NOTHING_OBSERVED,
+                mObserver.heightRequested);
+    }
+
+    @Test
     public void recordHistogramWindowResize_LayoutChangeInDesktopWindow() {
         // Simulate desktop windowing mode.
         mAppHeaderState = new AppHeaderState(new Rect(), new Rect(), /* isInDesktopWindow= */ true);
@@ -846,6 +897,10 @@
             doAnswer(args -> context.getResources().getDisplayMetrics().widthPixels)
                     .when(controlContainer)
                     .getWidth();
+            // Set a test height for the control container as if it's already being measured.
+            doReturn(TEST_TOOLBAR_HEIGHT + TEST_TAB_STRIP_HEIGHT)
+                    .when(controlContainer)
+                    .getHeight();
             doAnswer(
                             args -> {
                                 controlContainer.onLayoutChangeListener = args.getArgument(0);
diff --git a/chrome/browser/ui/bookmarks/bookmark_utils.cc b/chrome/browser/ui/bookmarks/bookmark_utils.cc
index 5a6446a..e7858be 100644
--- a/chrome/browser/ui/bookmarks/bookmark_utils.cc
+++ b/chrome/browser/ui/bookmarks/bookmark_utils.cc
@@ -292,17 +292,9 @@
   const gfx::VectorIcon* id;
   gfx::ImageSkia folder;
   if (icon_type == BookmarkFolderIconType::kNormal) {
-    id = features::IsChromeRefresh2023()
-             ? &vector_icons::kFolderChromeRefreshIcon
-             : (ui::TouchUiController::Get()->touch_ui()
-                    ? &vector_icons::kFolderTouchIcon
-                    : &vector_icons::kFolderIcon);
+    id = &vector_icons::kFolderChromeRefreshIcon;
   } else {
-    id = features::IsChromeRefresh2023()
-             ? &vector_icons::kFolderManagedRefreshIcon
-             : (ui::TouchUiController::Get()->touch_ui()
-                    ? &vector_icons::kFolderManagedTouchIcon
-                    : &vector_icons::kFolderManagedIcon);
+    id = &vector_icons::kFolderManagedRefreshIcon;
   }
   const ui::ThemedVectorIcon icon =
       absl::holds_alternative<SkColor>(color)
@@ -326,35 +318,8 @@
                             absl::variant<ui::ColorId, SkColor> color,
                             const ui::ColorProvider* color_provider) {
     gfx::ImageSkia folder;
-    if (features::IsChromeRefresh2023()) {
-      folder = GetBookmarkFolderImageFromVectorIcon(icon_type, color,
-                                                    color_provider);
-    } else {
-#if BUILDFLAG(IS_WIN)
-      // TODO(bsep): vectorize the Windows versions: crbug.com/564112
-      folder = *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
-          default_id);
-#elif BUILDFLAG(IS_MAC)
-      SkColor sk_color;
-      if (absl::holds_alternative<SkColor>(color)) {
-        sk_color = absl::get<SkColor>(color);
-      } else {
-        DCHECK(color_provider);
-        sk_color = color_provider->GetColor(absl::get<ui::ColorId>(color));
-      }
-      const int white_id = (icon_type == BookmarkFolderIconType::kNormal)
-                               ? IDR_FOLDER_CLOSED_WHITE
-                               : IDR_BOOKMARK_BAR_FOLDER_MANAGED_WHITE;
-      const int resource_id =
-          color_utils::IsDark(sk_color) ? default_id : white_id;
-      folder = *ui::ResourceBundle::GetSharedInstance()
-                    .GetNativeImageNamed(resource_id)
-                    .ToImageSkia();
-#else
-      folder = GetBookmarkFolderImageFromVectorIcon(icon_type, color,
-                                                    color_provider);
-#endif
-    }
+    folder =
+        GetBookmarkFolderImageFromVectorIcon(icon_type, color, color_provider);
     return gfx::ImageSkia(std::make_unique<RTLFlipSource>(folder),
                           folder.size());
   };
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 3cbc922e..66b78ef 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -78,6 +78,7 @@
 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
 #include "chrome/browser/ui/intent_picker_tab_helper.h"
 #include "chrome/browser/ui/lens/lens_overlay_controller.h"
+#include "chrome/browser/ui/lens/lens_overlay_invocation_source.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
 #include "chrome/browser/ui/qrcode_generator/qrcode_generator_bubble_controller.h"
@@ -2233,7 +2234,7 @@
   LensOverlayController* const controller =
       LensOverlayController::GetController(web_contents);
   CHECK(controller);
-  controller->ShowUI(LensOverlayController::InvocationSource::kAppMenu);
+  controller->ShowUI(lens::LensOverlayInvocationSource::kAppMenu);
   browser->window()->NotifyPromoFeatureUsed(lens::features::kLensOverlay);
 }
 
diff --git a/chrome/browser/ui/chromeos/magic_boost/BUILD.gn b/chrome/browser/ui/chromeos/magic_boost/BUILD.gn
index d41c7e5..96b9957 100644
--- a/chrome/browser/ui/chromeos/magic_boost/BUILD.gn
+++ b/chrome/browser/ui/chromeos/magic_boost/BUILD.gn
@@ -24,3 +24,18 @@
     "//ui/views",
   ]
 }
+
+source_set("test_support") {
+  testonly = true
+
+  sources = [
+    "test/mock_magic_boost_controller.cc",
+    "test/mock_magic_boost_controller.h",
+  ]
+
+  deps = [
+    ":magic_boost",
+    "//base",
+    "//testing/gmock",
+  ]
+}
diff --git a/chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.cc b/chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.cc
index 963a8ff..112022cb 100644
--- a/chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.cc
+++ b/chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.cc
@@ -10,8 +10,17 @@
 
 namespace chromeos {
 
+namespace {
+
+MagicBoostController* g_magic_boost_controller_for_testing = nullptr;
+
+}  // namespace
+
 // static
 MagicBoostController* MagicBoostController::Get() {
+  if (g_magic_boost_controller_for_testing) {
+    return g_magic_boost_controller_for_testing;
+  }
   static base::NoDestructor<MagicBoostController> instance;
   return instance.get();
 }
@@ -28,4 +37,27 @@
   disclaimer_widget_->Show();
 }
 
+bool MagicBoostController::ShouldQuickAnswersAndMahiShowOptIn() {
+  // TODO(b/339043693): Implement this function.
+  return false;
+}
+
+void MagicBoostController::SetAllFeaturesState(bool enabled) {
+  SetQuickAnswersAndMahiFeaturesState(enabled);
+  SetOrcaFeatureState(enabled);
+}
+
+void MagicBoostController::SetQuickAnswersAndMahiFeaturesState(bool enabled) {
+  // TODO(b/339043693): Implement this function.
+}
+
+ScopedMagicBoostControllerForTesting::ScopedMagicBoostControllerForTesting(
+    MagicBoostController* controller_for_testing) {
+  g_magic_boost_controller_for_testing = controller_for_testing;
+}
+
+ScopedMagicBoostControllerForTesting::~ScopedMagicBoostControllerForTesting() {
+  g_magic_boost_controller_for_testing = nullptr;
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.h b/chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.h
index 075b226..eb173fd 100644
--- a/chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.h
+++ b/chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_UI_CHROMEOS_MAGIC_BOOST_MAGIC_BOOST_CONTROLLER_H_
 #define CHROME_BROWSER_UI_CHROMEOS_MAGIC_BOOST_MAGIC_BOOST_CONTROLLER_H_
 
+#include "base/no_destructor.h"
 #include "ui/views/widget/unique_widget_ptr.h"
 
 namespace views {
@@ -14,6 +15,7 @@
 namespace chromeos {
 
 // The controller that manages the lifetime of opt-in and disclaimer widgets.
+// Some functions in this controller are virtual for testing.
 class MagicBoostController {
  public:
   MagicBoostController(const MagicBoostController&) = delete;
@@ -22,7 +24,10 @@
   static MagicBoostController* Get();
 
   // Shows Magic Boost opt-in widget.
-  void ShowOptInUi() {}
+  virtual void ShowOptInUi() {}
+
+  // Closes Magic Boost opt-in widget.
+  virtual void CloseOptInUi() {}
 
   // Shows Magic Boost disclaimer widget.
   void ShowDisclaimerUi();
@@ -32,6 +37,22 @@
     return disclaimer_widget_.get();
   }
 
+  // Closes Magic Boost disclaimer widget.
+  void CloseDisclaimerUi() {}
+
+  // Whether the Quick Answers and Mahi features should show the opt in UI.
+  virtual bool ShouldQuickAnswersAndMahiShowOptIn();
+
+  // Enables or disables all the features (including Quick Answers, Orca, and
+  // Mahi).
+  void SetAllFeaturesState(bool enabled);
+
+  // Enables or disables Quick Answers and Mahi.
+  void SetQuickAnswersAndMahiFeaturesState(bool enabled);
+
+  // Enables or disables Orca.
+  void SetOrcaFeatureState(bool enabled) {}
+
  protected:
   friend class base::NoDestructor<MagicBoostController>;
 
@@ -42,6 +63,15 @@
   views::UniqueWidgetPtr disclaimer_widget_;
 };
 
+// Helper class to automatically set and reset the `MagicBoostController` global
+// instance for testing.
+class ScopedMagicBoostControllerForTesting {
+ public:
+  explicit ScopedMagicBoostControllerForTesting(
+      MagicBoostController* controller_for_testing);
+  ~ScopedMagicBoostControllerForTesting();
+};
+
 }  // namespace chromeos
 
 #endif  // CHROME_BROWSER_UI_CHROMEOS_MAGIC_BOOST_MAGIC_BOOST_CONTROLLER_H_
diff --git a/chrome/browser/ui/chromeos/magic_boost/test/mock_magic_boost_controller.cc b/chrome/browser/ui/chromeos/magic_boost/test/mock_magic_boost_controller.cc
new file mode 100644
index 0000000..56a31081
--- /dev/null
+++ b/chrome/browser/ui/chromeos/magic_boost/test/mock_magic_boost_controller.cc
@@ -0,0 +1,13 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/chromeos/magic_boost/test/mock_magic_boost_controller.h"
+
+namespace chromeos {
+
+MockMagicBoostController::MockMagicBoostController() = default;
+
+MockMagicBoostController::~MockMagicBoostController() = default;
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/chromeos/magic_boost/test/mock_magic_boost_controller.h b/chrome/browser/ui/chromeos/magic_boost/test/mock_magic_boost_controller.h
new file mode 100644
index 0000000..2ed47df
--- /dev/null
+++ b/chrome/browser/ui/chromeos/magic_boost/test/mock_magic_boost_controller.h
@@ -0,0 +1,29 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_CHROMEOS_MAGIC_BOOST_TEST_MOCK_MAGIC_BOOST_CONTROLLER_H_
+#define CHROME_BROWSER_UI_CHROMEOS_MAGIC_BOOST_TEST_MOCK_MAGIC_BOOST_CONTROLLER_H_
+
+#include "chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace chromeos {
+
+// A mock class for testing.
+class MockMagicBoostController : public MagicBoostController {
+ public:
+  MockMagicBoostController();
+  MockMagicBoostController(const MockMagicBoostController&) = delete;
+  MockMagicBoostController& operator=(const MockMagicBoostController&) = delete;
+  ~MockMagicBoostController();
+
+  // chromeos::MahiManager:
+  MOCK_METHOD(void, ShowOptInUi, (), (override));
+  MOCK_METHOD(void, CloseOptInUi, (), (override));
+  MOCK_METHOD(bool, ShouldQuickAnswersAndMahiShowOptIn, (), (override));
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_CHROMEOS_MAGIC_BOOST_TEST_MOCK_MAGIC_BOOST_CONTROLLER_H_
diff --git a/chrome/browser/ui/commerce/product_specifications_entry_point_controller_browsertest.cc b/chrome/browser/ui/commerce/product_specifications_entry_point_controller_browsertest.cc
index e88aee1e..a6c5a27e5 100644
--- a/chrome/browser/ui/commerce/product_specifications_entry_point_controller_browsertest.cc
+++ b/chrome/browser/ui/commerce/product_specifications_entry_point_controller_browsertest.cc
@@ -128,8 +128,15 @@
   ASSERT_TRUE(controller_->entry_point_info_for_testing().has_value());
 }
 
+// TODO(b/341091285): Flaky on Win.
+#if BUILDFLAG(IS_WIN)
+#define MAYBE_TriggerEntryPointWithNavigation \
+  DISABLED_TriggerEntryPointWithNavigation
+#else
+#define MAYBE_TriggerEntryPointWithNavigation TriggerEntryPointWithNavigation
+#endif
 IN_PROC_BROWSER_TEST_F(ProductSpecificationsEntryPointControllerBrowserTest,
-                       TriggerEntryPointWithNavigation) {
+                       MAYBE_TriggerEntryPointWithNavigation) {
   // Mock EntryPointInfo returned by ShoppingService.
   std::set<GURL> urls = {GURL(kTestUrl2), GURL(kTestUrl3), GURL(kTestUrl4)};
   auto info = std::make_optional<commerce::EntryPointInfo>(kTitle, urls);
diff --git a/chrome/browser/ui/lens/BUILD.gn b/chrome/browser/ui/lens/BUILD.gn
index 2a7edad..4b2f1db 100644
--- a/chrome/browser/ui/lens/BUILD.gn
+++ b/chrome/browser/ui/lens/BUILD.gn
@@ -7,8 +7,10 @@
 
 static_library("lens") {
   sources = [
+    "lens_overlay_dismissal_source.h",
     "lens_overlay_image_helper.cc",
     "lens_overlay_image_helper.h",
+    "lens_overlay_invocation_source.h",
     "lens_overlay_permission_utils.cc",
     "lens_overlay_permission_utils.h",
     "lens_overlay_proto_converter.cc",
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc
index 0ea550f..11c1ab2 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -248,13 +248,14 @@
 }
 
 void LensOverlayController::ShowUIWithPendingRegion(
-    InvocationSource invocation_source,
+    lens::LensOverlayInvocationSource invocation_source,
     lens::mojom::CenterRotatedBoxPtr region) {
   pending_region_ = std::move(region);
   ShowUI(invocation_source);
 }
 
-void LensOverlayController::ShowUI(InvocationSource invocation_source) {
+void LensOverlayController::ShowUI(
+    lens::LensOverlayInvocationSource invocation_source) {
   // If UI is already showing or in the process of showing, do nothing.
   if (state_ != State::kOff) {
     return;
@@ -306,7 +307,7 @@
                           weak_factory_.GetWeakPtr()),
       base::BindRepeating(&LensOverlayController::HandleThumbnailCreated,
                           weak_factory_.GetWeakPtr()),
-      variations_client_, identity_manager_);
+      variations_client_, identity_manager_, invocation_source);
 
   side_panel_coordinator_ =
       SidePanelUtil::GetSidePanelCoordinatorForBrowser(tab_browser);
@@ -328,7 +329,8 @@
   base::UmaHistogramEnumeration("Lens.Overlay.Invoked", invocation_source);
 }
 
-void LensOverlayController::CloseUIAsync(DismissalSource dismissal_source) {
+void LensOverlayController::CloseUIAsync(
+    lens::LensOverlayDismissalSource dismissal_source) {
   if (state_ == State::kOff || state_ == State::kClosing) {
     return;
   }
@@ -528,7 +530,8 @@
   auto loaded_search_query =
       initialization_data_->currently_loaded_search_query_;
   if (loaded_search_query &&
-      loaded_search_query->search_query_url_ == search_url) {
+      lens::RemoveUrlViewportParams(loaded_search_query->search_query_url_) ==
+          lens::RemoveUrlViewportParams(search_url)) {
     return;
   }
 
@@ -600,7 +603,7 @@
     last_dismissal_source_.reset();
     return;
   }
-  CloseUIAsync(DismissalSource::kSidePanelCloseButton);
+  CloseUIAsync(lens::LensOverlayDismissalSource::kSidePanelCloseButton);
 }
 
 void LensOverlayController::IssueTextSelectionRequestForTesting(
@@ -635,12 +638,13 @@
     lens::LensOverlayInteractionResponseCallback interaction_data_callback,
     lens::LensOverlayThumbnailCreatedCallback thumbnail_created_callback,
     variations::VariationsClient* variations_client,
-    signin::IdentityManager* identity_manager) {
+    signin::IdentityManager* identity_manager,
+    lens::LensOverlayInvocationSource invocation_source) {
   return std::make_unique<lens::LensOverlayQueryController>(
       std::move(full_image_callback), std::move(url_callback),
       std::move(interaction_data_callback),
       std::move(thumbnail_created_callback), variations_client,
-      identity_manager);
+      identity_manager, invocation_source);
 }
 
 LensOverlayController::OverlayInitializationData::OverlayInitializationData(
@@ -688,7 +692,8 @@
 
   // content::WebContentsObserver
   void PrimaryPageChanged(content::Page& page) override {
-    lens_overlay_controller_->CloseUIAsync(DismissalSource::kPageChanged);
+    lens_overlay_controller_->CloseUIAsync(
+        lens::LensOverlayDismissalSource::kPageChanged);
   }
 
  private:
@@ -705,7 +710,8 @@
 
   // During initialization and shutdown a capture may not be possible.
   if (!view || !view->IsSurfaceAvailableForCopy()) {
-    CloseUIAsync(DismissalSource::kErrorScreenshotCreationFailed);
+    CloseUIAsync(
+        lens::LensOverlayDismissalSource::kErrorScreenshotCreationFailed);
   }
 
   state_ = State::kScreenshot;
@@ -737,7 +743,8 @@
   // this is a multi-process, multi-threaded environment so there may be a
   // TOCTTOU race condition.
   if (bitmap.drawsNothing()) {
-    CloseUIAsync(DismissalSource::kErrorScreenshotCreationFailed);
+    CloseUIAsync(
+        lens::LensOverlayDismissalSource::kErrorScreenshotCreationFailed);
     return;
   }
 
@@ -747,7 +754,8 @@
           bitmap, lens::features::GetLensOverlayScreenshotRenderQuality(),
           &data)) {
     // TODO(b/334185985): Handle case when screenshot data URI encoding fails.
-    CloseUIAsync(DismissalSource::kErrorScreenshotEncodingFailed);
+    CloseUIAsync(
+        lens::LensOverlayDismissalSource::kErrorScreenshotEncodingFailed);
     return;
   }
 
@@ -863,7 +871,8 @@
   SetWebViewCornerRadii(corner_radii_);
 }
 
-void LensOverlayController::CloseUIPart2(DismissalSource dismissal_source) {
+void LensOverlayController::CloseUIPart2(
+    lens::LensOverlayDismissalSource dismissal_source) {
   if (state_ == State::kOff) {
     return;
   }
@@ -939,7 +948,7 @@
 }
 
 void LensOverlayController::OnBackgroundUnblurred(
-    DismissalSource dismissal_source,
+    lens::LensOverlayDismissalSource dismissal_source,
     const viz::FrameTimingDetails& details) {
   // We only finish the closing process once the background has been unblurred.
   CloseUIPart2(dismissal_source);
@@ -1106,7 +1115,7 @@
   // If a side panel opens that is not ours, we must close the overlay.
   if (side_panel_coordinator_->GetCurrentEntryId() !=
       SidePanelEntry::Id::kLensOverlayResults) {
-    CloseUIAsync(DismissalSource::kUnexpectedSidePanelOpen);
+    CloseUIAsync(lens::LensOverlayDismissalSource::kUnexpectedSidePanelOpen);
   }
 }
 
@@ -1115,7 +1124,7 @@
   // opened in the process, we need to close the overlay to not show next to the
   // unwanted side panel.
   if (state_ == State::kClosingOpenedSidePanel) {
-    CloseUIAsync(DismissalSource::kUnexpectedSidePanelOpen);
+    CloseUIAsync(lens::LensOverlayDismissalSource::kUnexpectedSidePanelOpen);
   }
 }
 
@@ -1154,7 +1163,8 @@
   // This is still possible when the controller is in state kScreenshot and the
   // tab was backgrounded. We should close the UI as the overlay has not been
   // created yet.
-  CloseUIAsync(DismissalSource::kTabBackgroundedWhileScreenshotting);
+  CloseUIAsync(
+      lens::LensOverlayDismissalSource::kTabBackgroundedWhileScreenshotting);
 }
 
 void LensOverlayController::WillDiscardContents(
@@ -1162,7 +1172,7 @@
     content::WebContents* old_contents,
     content::WebContents* new_contents) {
   // Background tab contents discarded.
-  CloseUIAsync(DismissalSource::kTabContentsDiscarded);
+  CloseUIAsync(lens::LensOverlayDismissalSource::kTabContentsDiscarded);
   old_contents->RemoveUserData(LensOverlayControllerTabLookup::UserDataKey());
   LensOverlayControllerTabLookup::CreateForWebContents(new_contents, this);
 }
@@ -1202,19 +1212,19 @@
 }
 
 void LensOverlayController::CloseRequestedByOverlayCloseButton() {
-  CloseUIAsync(DismissalSource::kOverlayCloseButton);
+  CloseUIAsync(lens::LensOverlayDismissalSource::kOverlayCloseButton);
 }
 
 void LensOverlayController::CloseRequestedByOverlayBackgroundClick() {
-  CloseUIAsync(DismissalSource::kOverlayBackgroundClick);
+  CloseUIAsync(lens::LensOverlayDismissalSource::kOverlayBackgroundClick);
 }
 
 void LensOverlayController::CloseRequestedByOverlayEscapeKeyPress() {
-  CloseUIAsync(DismissalSource::kEscapeKeyPress);
+  CloseUIAsync(lens::LensOverlayDismissalSource::kEscapeKeyPress);
 }
 
 void LensOverlayController::CloseRequestedBySidePanelEscapeKeyPress() {
-  CloseUIAsync(DismissalSource::kEscapeKeyPress);
+  CloseUIAsync(lens::LensOverlayDismissalSource::kEscapeKeyPress);
 }
 
 void LensOverlayController::FeedbackRequestedByOverlay() {
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.h b/chrome/browser/ui/lens/lens_overlay_controller.h
index 154f3a6..575eb11 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.h
+++ b/chrome/browser/ui/lens/lens_overlay_controller.h
@@ -10,6 +10,8 @@
 #include "chrome/browser/lens/core/mojom/lens.mojom.h"
 #include "chrome/browser/lens/core/mojom/overlay_object.mojom.h"
 #include "chrome/browser/lens/core/mojom/text.mojom.h"
+#include "chrome/browser/ui/lens/lens_overlay_dismissal_source.h"
+#include "chrome/browser/ui/lens/lens_overlay_invocation_source.h"
 #include "chrome/browser/ui/lens/lens_overlay_query_controller.h"
 #include "chrome/browser/ui/tabs/public/tab_interface.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
@@ -102,101 +104,21 @@
   // Returns whether the lens overlay feature is enabled.
   static bool IsEnabled(Profile* profile);
 
-  // Designates the source of any lens overlay invocation (in other words, any
-  // call to `ShowUI()`).
-  //
-  // These values are persisted to logs. Entries should not be renumbered and
-  // numeric values should never be reused.
-  //
-  // LINT.IfChange(InvocationSource)
-  enum class InvocationSource {
-    // The Chrome app ("3-dot") menu entry.
-    kAppMenu = 0,
-
-    // The content area context menu entry that is available when the user
-    // right-clicks on any area of the page that doesn't contain text, links or
-    // media.
-    kContentAreaContextMenuPage = 1,
-
-    // The content area context menu entry that is available when the user
-    // right-clicks on an image.
-    kContentAreaContextMenuImage = 2,
-
-    // The pinned toolbar action button.
-    kToolbar = 3,
-
-    // The find in page (Ctrl/Cmd-f) dialog button.
-    kFindInPage = 4,
-
-    // The button in the omnibox (address bar).
-    kOmnibox = 5,
-
-    kMaxValue = kOmnibox
-  };
-  // LINT.ThenChange(//tools/metrics/histograms/metadata/others/enums.xml:LensOverlayInvocationSource)
-
   // Sets a region to search after the overlay loads, then calls ShowUI().
-  void ShowUIWithPendingRegion(InvocationSource invocation_source,
-                               lens::mojom::CenterRotatedBoxPtr region);
+  void ShowUIWithPendingRegion(
+      lens::LensOverlayInvocationSource invocation_source,
+      lens::mojom::CenterRotatedBoxPtr region);
 
   // This is entry point for showing the overlay UI. This has no effect if state
   // is not kOff. This has no effect if the tab is not in the foreground. If the
   // overlay is successfully invoked, then the value of `invocation_source` will
   // be recorded in the relevant metrics.
-  void ShowUI(InvocationSource invocation_source);
-
-  // Designates the source of any lens overlay dismissal (in other words, any
-  // call to `CloseUI()`).
-  //
-  // These values are persisted to logs. Entries should not be renumbered and
-  // numeric values should never be reused.
-  //
-  // LINT.IfChange(DismissalSource)
-  enum class DismissalSource {
-    // The overlay close button (shown when in the kOverlay state).
-    kOverlayCloseButton = 0,
-
-    // A click on the background scrim (shown when in the kOverlayAndResults
-    // state).
-    kOverlayBackgroundClick = 1,
-
-    // The close button in the side panel.
-    kSidePanelCloseButton = 2,
-
-    // The pinned toolbar action button.
-    kToolbar = 3,
-
-    // The page in the primary web contents changed (link clicked, back button,
-    // etc.).
-    kPageChanged = 4,
-
-    // The contents of the associated tab were in the background and discarded
-    // to save memory.
-    kTabContentsDiscarded = 5,
-
-    // The current tab was backgrounded before the screenshot was created.
-    kTabBackgroundedWhileScreenshotting = 6,
-
-    // Creating a screenshot from the view of the web contents failed.
-    kErrorScreenshotCreationFailed = 7,
-
-    // Encoding the screenshot failed.
-    kErrorScreenshotEncodingFailed = 8,
-
-    // User pressed the escape key.
-    kEscapeKeyPress = 9,
-
-    // Another side panel opened forcing our overlay to close.
-    kUnexpectedSidePanelOpen = 10,
-
-    kMaxValue = kUnexpectedSidePanelOpen
-  };
-  // LINT.ThenChange(//tools/metrics/histograms/metadata/others/enums.xml:LensOverlayDismissalSource)
+  void ShowUI(lens::LensOverlayInvocationSource invocation_source);
 
   // Starts the closing process of the overlay. This is an asynchronous process
   // because we first must unblur the background, before closing the overlay
   // whether. Eventually Calls CloseUI() asynchronously.
-  void CloseUIAsync(DismissalSource dismissal_source);
+  void CloseUIAsync(lens::LensOverlayDismissalSource dismissal_source);
 
   // Given an instance of `web_ui` created by the LensOverlayController, returns
   // the LensOverlayController. This method is necessary because WebUIController
@@ -423,7 +345,8 @@
       lens::LensOverlayInteractionResponseCallback interaction_data_callback,
       lens::LensOverlayThumbnailCreatedCallback thumbnail_created_callback,
       variations::VariationsClient* variations_client,
-      signin::IdentityManager* identity_manager);
+      signin::IdentityManager* identity_manager,
+      lens::LensOverlayInvocationSource invocation_source);
 
  private:
   // Data class for constructing overlay and storing overlay state for
@@ -529,11 +452,11 @@
   // cleanup of closing the overlay UI and should only be called by
   // CloseUIAsync. Anyone called trying to close the UI should go through
   // CloseUIAsync.
-  void CloseUIPart2(DismissalSource dismissal_source);
+  void CloseUIPart2(lens::LensOverlayDismissalSource dismissal_source);
 
   // Passed into the compositor layer to know when the background is done being
   // unblurred and it is safe to close the overlay.
-  void OnBackgroundUnblurred(DismissalSource dismissal_source,
+  void OnBackgroundUnblurred(lens::LensOverlayDismissalSource dismissal_source,
                              const viz::FrameTimingDetails& details);
 
   // Initializes the overlay UI after it has been created with data fetched
@@ -692,7 +615,7 @@
   // If the side panel needed to be closed before dismissing the overlay, this
   // stores the original dismissal_source so it is properly recorded when the
   // side panel is done closing and the callback is invoked.
-  std::optional<DismissalSource> last_dismissal_source_;
+  std::optional<lens::LensOverlayDismissalSource> last_dismissal_source_;
 
   // Thumbnail URI referencing the data defined by the user image selection on
   // the overlay. If the user hasn't made any selection or has made a text
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
index e32b0a10..3ac2c7c 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -21,6 +21,8 @@
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
+#include "chrome/browser/ui/lens/lens_overlay_dismissal_source.h"
+#include "chrome/browser/ui/lens/lens_overlay_invocation_source.h"
 #include "chrome/browser/ui/lens/lens_overlay_permission_utils.h"
 #include "chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.h"
 #include "chrome/browser/ui/lens/lens_overlay_url_builder.h"
@@ -66,8 +68,8 @@
 constexpr char kDocumentWithNamedElement[] = "/select.html";
 
 using State = LensOverlayController::State;
-using InvocationSource = LensOverlayController::InvocationSource;
-using DismissalSource = LensOverlayController::DismissalSource;
+using LensOverlayInvocationSource = lens::LensOverlayInvocationSource;
+using LensOverlayDismissalSource = lens::LensOverlayDismissalSource;
 
 constexpr char kNewTabLinkClickScript[] =
     "(function() {const anchor = document.createElement('a');anchor.href = "
@@ -234,13 +236,15 @@
       lens::LensOverlayInteractionResponseCallback interaction_data_callback,
       lens::LensOverlayThumbnailCreatedCallback thumbnail_created_callback,
       variations::VariationsClient* variations_client,
-      signin::IdentityManager* identity_manager)
+      signin::IdentityManager* identity_manager,
+      LensOverlayInvocationSource invocation_source)
       : LensOverlayQueryController(full_image_callback,
                                    url_callback,
                                    interaction_data_callback,
                                    thumbnail_created_callback,
                                    variations_client,
-                                   identity_manager) {}
+                                   identity_manager,
+                                   invocation_source) {}
 
   void StartQueryFlow(const SkBitmap& screenshot,
                       std::optional<GURL> page_url,
@@ -282,10 +286,12 @@
       lens::LensOverlayInteractionResponseCallback interaction_data_callback,
       lens::LensOverlayThumbnailCreatedCallback thumbnail_created_callback,
       variations::VariationsClient* variations_client,
-      signin::IdentityManager* identity_manager) override {
+      signin::IdentityManager* identity_manager,
+      lens::LensOverlayInvocationSource invocation_source) override {
     return std::make_unique<LensOverlayQueryControllerFake>(
         full_image_callback, url_callback, interaction_data_callback,
-        thumbnail_created_callback, variations_client, identity_manager);
+        thumbnail_created_callback, variations_client, identity_manager,
+        invocation_source);
   }
 
   void BindOverlay(mojo::PendingReceiver<lens::mojom::LensPageHandler> receiver,
@@ -481,7 +487,7 @@
   }
 
   void CloseOverlayAndWaitForOff(LensOverlayController* controller,
-                                 DismissalSource dismissal_source) {
+                                 LensOverlayDismissalSource dismissal_source) {
     controller->CloseUIAsync(dismissal_source);
     ASSERT_TRUE(base::test::RunUntil(
         [&]() { return controller->state() == State::kOff; }));
@@ -510,7 +516,7 @@
   // Verify attempting to show the UI will show the permission bubble.
   views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
                                        lens::kLensPermissionDialogName);
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   // State should remain off.
   ASSERT_EQ(controller->state(), State::kOff);
   auto* bubble_widget = waiter.WaitIfNeededAndGet();
@@ -521,7 +527,7 @@
                   ->HasOpenDialogWidget());
 
   // Verify attempting to show the UI again does not close the bubble widget.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   // State should remain off.
   ASSERT_EQ(controller->state(), State::kOff);
   ASSERT_TRUE(bubble_widget->IsVisible());
@@ -570,7 +576,7 @@
   // Verify attempting to show the UI will show the permission bubble.
   views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
                                        lens::kLensPermissionDialogName);
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   // State should remain off.
   ASSERT_EQ(controller->state(), State::kOff);
   auto* bubble_widget = waiter.WaitIfNeededAndGet();
@@ -581,7 +587,7 @@
                   ->HasOpenDialogWidget());
 
   // Verify attempting to show the UI again does not close the bubble widget.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   // State should remain off.
   ASSERT_EQ(controller->state(), State::kOff);
   ASSERT_TRUE(bubble_widget->IsVisible());
@@ -621,7 +627,7 @@
   // Verify attempting to show the UI will show the permission bubble.
   views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
                                        lens::kLensPermissionDialogName);
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   // State should remain off.
   ASSERT_EQ(controller->state(), State::kOff);
   auto* bubble_widget = waiter.WaitIfNeededAndGet();
@@ -632,7 +638,7 @@
                   ->HasOpenDialogWidget());
 
   // Verify attempting to show the UI again does not close the bubble widget.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   // State should remain off.
   ASSERT_EQ(controller->state(), State::kOff);
   ASSERT_TRUE(bubble_widget->IsVisible());
@@ -670,7 +676,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -698,7 +704,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -721,7 +727,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -759,7 +765,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUIWithPendingRegion(InvocationSource::kAppMenu,
+  controller->ShowUIWithPendingRegion(LensOverlayInvocationSource::kAppMenu,
                                       kTestRegion->Clone());
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
@@ -790,7 +796,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -831,7 +837,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -852,7 +858,8 @@
   EXPECT_FALSE(observer.request_shown());
 
   // Close overlay
-  CloseOverlayAndWaitForOff(controller, DismissalSource::kOverlayCloseButton);
+  CloseOverlayAndWaitForOff(controller,
+                            LensOverlayDismissalSource::kOverlayCloseButton);
 
   // Verify a prompt was shown
   ASSERT_TRUE(base::test::RunUntil([&]() { return observer.request_shown(); }));
@@ -880,7 +887,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   EXPECT_TRUE(controller->GetThumbnailForTesting().empty());
   EXPECT_EQ(controller->GetPageClassificationForTesting(),
@@ -973,7 +980,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -1023,7 +1030,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -1075,7 +1082,7 @@
   // Showing UI should change the state to screenshot and eventually to overlay.
   // When the overlay is bound, it should start the query flow which returns a
   // response for the full image callback.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -1116,7 +1123,7 @@
   // Showing UI should change the state to screenshot and eventually to overlay.
   // When the overlay is bound, it should start the query flow which returns a
   // response for the full image callback.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -1150,7 +1157,7 @@
       browser()->tab_strip_model()->active_index();
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -1201,7 +1208,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -1256,7 +1263,7 @@
   EXPECT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   EXPECT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -1338,7 +1345,7 @@
   EXPECT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   EXPECT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -1402,7 +1409,7 @@
   EXPECT_EQ(controller->state(), State::kOff);
 
   // Showing UI should change the state to screenshot and eventually to overlay.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_EQ(controller->state(), State::kScreenshot);
   EXPECT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -1467,7 +1474,7 @@
   EXPECT_EQ(controller->state(), State::kOff);
 
   // Showing UI should eventually result in overlay state.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   EXPECT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
   EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents()));
@@ -1532,7 +1539,7 @@
   // Showing UI should eventually result in overlay state. When the overlay is
   // bound, it should start the query flow which returns a response for the
   // interaction data callback.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
   ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents()));
@@ -1573,7 +1580,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should eventually result in overlay state.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   EXPECT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
   EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents()));
@@ -1582,7 +1589,8 @@
   // Loading a url in the side panel should show the results page.
   const GURL first_search_url(
       "https://www.google.com/"
-      "search?q=oranges&lns_mode=text&gsc=1&masfc=c&hl=en-US");
+      "search?source=chrome.cr.menu&q=oranges&lns_mode=text&gsc=1&masfc=c&"
+      "hl=en-US");
   controller->LoadURLInResultsFrame(first_search_url);
   EXPECT_TRUE(content::WaitForLoadStop(
       controller->GetSidePanelWebContentsForTesting()));
@@ -1603,7 +1611,8 @@
   // Loading a second url in the side panel should show the results page.
   const GURL second_search_url(
       "https://www.google.com/"
-      "search?q=kiwi&lns_mode=text&gsc=1&masfc=c&hl=en-US");
+      "search?source=chrome.cr.menu&q=kiwi&lns_mode=text&gsc=1&masfc=c&hl="
+      "en-US");
   // We can't use content::WaitForLoadStop here since the last navigation is
   // successful.
   content::TestNavigationObserver observer(
@@ -1661,7 +1670,7 @@
   ASSERT_EQ(controller->state(), State::kOff);
 
   // Showing UI should eventually result in overlay state.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   EXPECT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
   EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents()));
@@ -1670,7 +1679,8 @@
   // Loading a url in the side panel should show the results page.
   const GURL first_search_url(
       "https://www.google.com/"
-      "search?q=oranges&lns_mode=text&gsc=1&masfc=c&hl=en-US");
+      "search?source=chrome.cr.menu&q=oranges&lns_mode=text&gsc=1&masfc=c&"
+      "hl=en-US");
   controller->IssueTextSelectionRequestForTesting("oranges", 20, 200);
   EXPECT_TRUE(content::WaitForLoadStop(
       controller->GetSidePanelWebContentsForTesting()));
@@ -1693,7 +1703,8 @@
   // Loading a second url in the side panel should show the results page.
   const GURL second_search_url(
       "https://www.google.com/"
-      "search?q=kiwi&lns_mode=text&gsc=1&masfc=c&hl=en-US");
+      "search?source=chrome.cr.menu&q=kiwi&lns_mode=text&gsc=1&masfc=c&hl="
+      "en-US");
   // We can't use content::WaitForLoadStop here since the last navigation is
   // successful.
   content::TestNavigationObserver observer(
@@ -1754,6 +1765,57 @@
 }
 
 IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
+                       AddQueryToHistoryAfterResize) {
+  WaitForPaint();
+
+  // State should start in off.
+  auto* controller = browser()
+                         ->tab_strip_model()
+                         ->GetActiveTab()
+                         ->tab_features()
+                         ->lens_overlay_controller();
+  ASSERT_EQ(controller->state(), State::kOff);
+
+  // Showing UI should eventually result in overlay state.
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
+  EXPECT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+  EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents()));
+  EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible());
+
+  // Loading a url in the side panel should show the results page.
+  const GURL first_search_url(
+      "https://www.google.com/search?q=oranges&gsc=1&masfc=c&hl=en-US");
+  controller->LoadURLInResultsFrame(first_search_url);
+  EXPECT_TRUE(content::WaitForLoadStop(
+      controller->GetSidePanelWebContentsForTesting()));
+
+  // Loading a second url in the side panel should show the results page.
+  const GURL second_search_url(
+      "https://www.google.com/search?q=kiwi&gsc=1&masfc=c&hl=en-US");
+  // We can't use content::WaitForLoadStop here since the last navigation is
+  // successful.
+  content::TestNavigationObserver observer(
+      controller->GetSidePanelWebContentsForTesting());
+  controller->LoadURLInResultsFrame(second_search_url);
+  observer.WaitForNavigationFinished();
+
+  // Make the side panel larger.
+  const int increment = -50;
+  BrowserView::GetBrowserViewForBrowser(browser())
+      ->unified_side_panel()
+      ->OnResize(increment, true);
+  // Popping the query should load the previous query into the results frame.
+  content::TestNavigationObserver pop_observer(
+      controller->GetSidePanelWebContentsForTesting());
+  controller->PopAndLoadQueryFromHistory();
+  pop_observer.WaitForNavigationFinished();
+  // The search query history stack should be empty and the currently loaded
+  // query should be set to the previous query.
+  EXPECT_TRUE(controller->GetSearchQueryHistoryForTesting().empty());
+}
+
+IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest,
                        RecordInvocationAndDismissalHistograms) {
   base::HistogramTester histogram_tester;
   WaitForPaint();
@@ -1769,117 +1831,128 @@
   // Showing the UI and then closing it should record an entry in the
   // appropriate buckets and the total count of invocations and dismissals
   // should be 1.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
   ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents()));
   histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked",
-                                     InvocationSource::kAppMenu,
+                                     LensOverlayInvocationSource::kAppMenu,
                                      /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked",
                                     /*expected_count=*/1);
-  CloseOverlayAndWaitForOff(controller, DismissalSource::kOverlayCloseButton);
-  histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed",
-                                     DismissalSource::kOverlayCloseButton,
-                                     /*expected_count=*/1);
+  CloseOverlayAndWaitForOff(controller,
+                            LensOverlayDismissalSource::kOverlayCloseButton);
+  histogram_tester.ExpectBucketCount(
+      "Lens.Overlay.Dismissed", LensOverlayDismissalSource::kOverlayCloseButton,
+      /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed",
                                     /*expected_count=*/1);
 
   // Attempting to invoke the overlay twice without closing it in between
   // should record only a single new entry.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked",
-                                     InvocationSource::kAppMenu,
+                                     LensOverlayInvocationSource::kAppMenu,
                                      /*expected_count=*/2);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked",
                                     /*expected_count=*/2);
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked",
-                                     InvocationSource::kAppMenu,
+                                     LensOverlayInvocationSource::kAppMenu,
                                      /*expected_count=*/2);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked",
                                     /*expected_count=*/2);
 
   // Attempting to close the overlay twice without opening it in between should
   // only record a single entry.
-  CloseOverlayAndWaitForOff(controller, DismissalSource::kOverlayCloseButton);
-  histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed",
-                                     DismissalSource::kOverlayCloseButton,
-                                     /*expected_count=*/2);
+  CloseOverlayAndWaitForOff(controller,
+                            LensOverlayDismissalSource::kOverlayCloseButton);
+  histogram_tester.ExpectBucketCount(
+      "Lens.Overlay.Dismissed", LensOverlayDismissalSource::kOverlayCloseButton,
+      /*expected_count=*/2);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed",
                                     /*expected_count=*/2);
-  CloseOverlayAndWaitForOff(controller, DismissalSource::kOverlayCloseButton);
-  histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed",
-                                     DismissalSource::kOverlayCloseButton,
-                                     /*expected_count=*/2);
+  CloseOverlayAndWaitForOff(controller,
+                            LensOverlayDismissalSource::kOverlayCloseButton);
+  histogram_tester.ExpectBucketCount(
+      "Lens.Overlay.Dismissed", LensOverlayDismissalSource::kOverlayCloseButton,
+      /*expected_count=*/2);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed",
                                     /*expected_count=*/2);
 
   // Each type of invocation and dismissal should record entries in the
   // appropriate buckets.
-  controller->ShowUI(InvocationSource::kContentAreaContextMenuPage);
+  controller->ShowUI(LensOverlayInvocationSource::kContentAreaContextMenuPage);
   histogram_tester.ExpectBucketCount(
-      "Lens.Overlay.Invoked", InvocationSource::kContentAreaContextMenuPage,
+      "Lens.Overlay.Invoked",
+      LensOverlayInvocationSource::kContentAreaContextMenuPage,
       /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked",
                                     /*expected_count=*/3);
+  CloseOverlayAndWaitForOff(
+      controller, LensOverlayDismissalSource::kOverlayBackgroundClick);
+  histogram_tester.ExpectBucketCount(
+      "Lens.Overlay.Dismissed",
+      LensOverlayDismissalSource::kOverlayBackgroundClick,
+      /*expected_count=*/1);
+  histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed",
+                                    /*expected_count=*/3);
+
+  controller->ShowUI(LensOverlayInvocationSource::kContentAreaContextMenuImage);
+  histogram_tester.ExpectBucketCount(
+      "Lens.Overlay.Invoked",
+      LensOverlayInvocationSource::kContentAreaContextMenuImage,
+      /*expected_count=*/1);
+  histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked",
+                                    /*expected_count=*/4);
   CloseOverlayAndWaitForOff(controller,
-                            DismissalSource::kOverlayBackgroundClick);
-  histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed",
-                                     DismissalSource::kOverlayBackgroundClick,
-                                     /*expected_count=*/1);
-  histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed",
-                                    /*expected_count=*/3);
-
-  controller->ShowUI(InvocationSource::kContentAreaContextMenuImage);
+                            LensOverlayDismissalSource::kSidePanelCloseButton);
   histogram_tester.ExpectBucketCount(
-      "Lens.Overlay.Invoked", InvocationSource::kContentAreaContextMenuImage,
+      "Lens.Overlay.Dismissed",
+      LensOverlayDismissalSource::kSidePanelCloseButton,
       /*expected_count=*/1);
-  histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked",
-                                    /*expected_count=*/4);
-  CloseOverlayAndWaitForOff(controller, DismissalSource::kSidePanelCloseButton);
-  histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed",
-                                     DismissalSource::kSidePanelCloseButton,
-                                     /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed",
                                     /*expected_count=*/4);
 
-  controller->ShowUI(InvocationSource::kToolbar);
+  controller->ShowUI(LensOverlayInvocationSource::kToolbar);
   histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked",
-                                     InvocationSource::kToolbar,
+                                     LensOverlayInvocationSource::kToolbar,
                                      /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked",
                                     /*expected_count=*/5);
-  CloseOverlayAndWaitForOff(controller, DismissalSource::kToolbar);
+  CloseOverlayAndWaitForOff(controller, LensOverlayDismissalSource::kToolbar);
   histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed",
-                                     DismissalSource::kToolbar,
+                                     LensOverlayDismissalSource::kToolbar,
                                      /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed",
                                     /*expected_count=*/5);
 
-  controller->ShowUI(InvocationSource::kFindInPage);
+  controller->ShowUI(LensOverlayInvocationSource::kFindInPage);
   histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked",
-                                     InvocationSource::kFindInPage,
+                                     LensOverlayInvocationSource::kFindInPage,
                                      /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked",
                                     /*expected_count=*/6);
-  CloseOverlayAndWaitForOff(controller, DismissalSource::kPageChanged);
+  CloseOverlayAndWaitForOff(controller,
+                            LensOverlayDismissalSource::kPageChanged);
   histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed",
-                                     DismissalSource::kPageChanged,
+                                     LensOverlayDismissalSource::kPageChanged,
                                      /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed",
                                     /*expected_count=*/6);
 
-  controller->ShowUI(InvocationSource::kOmnibox);
+  controller->ShowUI(LensOverlayInvocationSource::kOmnibox);
   histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked",
-                                     InvocationSource::kOmnibox,
+                                     LensOverlayInvocationSource::kOmnibox,
                                      /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked",
                                     /*expected_count=*/7);
-  CloseOverlayAndWaitForOff(controller, DismissalSource::kTabContentsDiscarded);
-  histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed",
-                                     DismissalSource::kTabContentsDiscarded,
-                                     /*expected_count=*/1);
+  CloseOverlayAndWaitForOff(controller,
+                            LensOverlayDismissalSource::kTabContentsDiscarded);
+  histogram_tester.ExpectBucketCount(
+      "Lens.Overlay.Dismissed",
+      LensOverlayDismissalSource::kTabContentsDiscarded,
+      /*expected_count=*/1);
   histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed",
                                     /*expected_count=*/7);
 }
@@ -1914,7 +1987,7 @@
   // Showing UI should eventually result in overlay state. When the overlay is
   // bound, it should start the query flow which returns a response for the
   // interaction data callback.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
 
@@ -1937,7 +2010,7 @@
   // Showing UI should eventually result in overlay state. When the overlay is
   // bound, it should start the query flow which returns a response for the
   // interaction data callback.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
 
@@ -1968,7 +2041,7 @@
   // Showing UI should eventually result in overlay state. When the overlay is
   // bound, it should start the query flow which returns a response for the
   // interaction data callback.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
 
@@ -2033,7 +2106,7 @@
   //   Showing UI should eventually result in overlay state. When the overlay is
   //   bound, it should start the query flow which returns a response for the
   //   interaction data callback.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
   ASSERT_TRUE(controller->state() == State::kClosingOpenedSidePanel);
 
   // Wait for the side panel to start closing.
@@ -2049,7 +2122,7 @@
   // Secondly, test the flow if the side panel is open with our results, if we
   // close our UI and request the close the side panel, we gracefully handle a
   // new side panel opening which prevents our requested close.
-  controller->ShowUI(InvocationSource::kAppMenu);
+  controller->ShowUI(LensOverlayInvocationSource::kAppMenu);
 
   ASSERT_TRUE(base::test::RunUntil(
       [&]() { return controller->state() == State::kOverlay; }));
@@ -2063,7 +2136,7 @@
       [&]() { return controller->state() == State::kOverlayAndResults; }));
 
   // Request a close which will start to close the side panel.
-  controller->CloseUIAsync(DismissalSource::kOverlayBackgroundClick);
+  controller->CloseUIAsync(LensOverlayDismissalSource::kOverlayBackgroundClick);
   ASSERT_TRUE(controller->state() == State::kClosingSidePanel);
 
   // Reshow the side panel to prevent a the side panel from closing.
diff --git a/chrome/browser/ui/lens/lens_overlay_dismissal_source.h b/chrome/browser/ui/lens/lens_overlay_dismissal_source.h
new file mode 100644
index 0000000..f0936bd8
--- /dev/null
+++ b/chrome/browser/ui/lens/lens_overlay_dismissal_source.h
@@ -0,0 +1,60 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_LENS_LENS_OVERLAY_DISMISSAL_SOURCE_H_
+#define CHROME_BROWSER_UI_LENS_LENS_OVERLAY_DISMISSAL_SOURCE_H_
+
+namespace lens {
+
+// Designates the source of any lens overlay dismissal (in other words, any
+// call to `LensOverlayController:CloseUI()`).
+//
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+//
+// LINT.IfChange(LensOverlayDismissalSource)
+enum class LensOverlayDismissalSource {
+  // The overlay close button (shown when in the kOverlay state).
+  kOverlayCloseButton = 0,
+
+  // A click on the background scrim (shown when in the kOverlayAndResults
+  // state).
+  kOverlayBackgroundClick = 1,
+
+  // The close button in the side panel.
+  kSidePanelCloseButton = 2,
+
+  // The pinned toolbar action button.
+  kToolbar = 3,
+
+  // The page in the primary web contents changed (link clicked, back button,
+  // etc.).
+  kPageChanged = 4,
+
+  // The contents of the associated tab were in the background and discarded
+  // to save memory.
+  kTabContentsDiscarded = 5,
+
+  // The current tab was backgrounded before the screenshot was created.
+  kTabBackgroundedWhileScreenshotting = 6,
+
+  // Creating a screenshot from the view of the web contents failed.
+  kErrorScreenshotCreationFailed = 7,
+
+  // Encoding the screenshot failed.
+  kErrorScreenshotEncodingFailed = 8,
+
+  // User pressed the escape key.
+  kEscapeKeyPress = 9,
+
+  // Another side panel opened forcing our overlay to close.
+  kUnexpectedSidePanelOpen = 10,
+
+  kMaxValue = kUnexpectedSidePanelOpen
+};
+// LINT.ThenChange(//tools/metrics/histograms/metadata/others/enums.xml:LensOverlayDismissalSource)
+
+}  // namespace lens
+
+#endif  // CHROME_BROWSER_UI_LENS_LENS_OVERLAY_DISMISSAL_SOURCE_H_
diff --git a/chrome/browser/ui/lens/lens_overlay_invocation_source.h b/chrome/browser/ui/lens/lens_overlay_invocation_source.h
new file mode 100644
index 0000000..24b59cd
--- /dev/null
+++ b/chrome/browser/ui/lens/lens_overlay_invocation_source.h
@@ -0,0 +1,45 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_LENS_LENS_OVERLAY_INVOCATION_SOURCE_H_
+#define CHROME_BROWSER_UI_LENS_LENS_OVERLAY_INVOCATION_SOURCE_H_
+
+namespace lens {
+
+// Designates the source of any lens overlay invocation (in other words, any
+// call to `LensOverlayController::ShowUI()`).
+//
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+//
+// LINT.IfChange(LensOverlayInvocationSource)
+enum class LensOverlayInvocationSource {
+  // The Chrome app ("3-dot") menu entry.
+  kAppMenu = 0,
+
+  // The content area context menu entry that is available when the user
+  // right-clicks on any area of the page that doesn't contain text, links or
+  // media.
+  kContentAreaContextMenuPage = 1,
+
+  // The content area context menu entry that is available when the user
+  // right-clicks on an image.
+  kContentAreaContextMenuImage = 2,
+
+  // The pinned toolbar action button.
+  kToolbar = 3,
+
+  // The find in page (Ctrl/Cmd-f) dialog button.
+  kFindInPage = 4,
+
+  // The button in the omnibox (address bar).
+  kOmnibox = 5,
+
+  kMaxValue = kOmnibox
+};
+// LINT.ThenChange(//tools/metrics/histograms/metadata/others/enums.xml:LensOverlayInvocationSource)
+
+}  // namespace lens
+
+#endif  // CHROME_BROWSER_UI_LENS_LENS_OVERLAY_INVOCATION_SOURCE_H_
diff --git a/chrome/browser/ui/lens/lens_overlay_query_controller.cc b/chrome/browser/ui/lens/lens_overlay_query_controller.cc
index 61f080f..8091fe8c 100644
--- a/chrome/browser/ui/lens/lens_overlay_query_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_query_controller.cc
@@ -151,7 +151,8 @@
     LensOverlayInteractionResponseCallback interaction_data_callback,
     LensOverlayThumbnailCreatedCallback thumbnail_created_callback,
     variations::VariationsClient* variations_client,
-    signin::IdentityManager* identity_manager)
+    signin::IdentityManager* identity_manager,
+    lens::LensOverlayInvocationSource invocation_source)
     : full_image_callback_(std::move(full_image_callback)),
       interaction_data_callback_(std::move(interaction_data_callback)),
       thumbnail_created_callback_(std::move(thumbnail_created_callback)),
@@ -159,7 +160,8 @@
           std::make_unique<lens::LensOverlayRequestIdGenerator>()),
       url_callback_(std::move(url_callback)),
       variations_client_(variations_client),
-      identity_manager_{identity_manager} {}
+      identity_manager_(identity_manager),
+      invocation_source_(invocation_source) {}
 
 LensOverlayQueryController::~LensOverlayQueryController() = default;
 
@@ -382,7 +384,8 @@
   lens::proto::LensOverlayUrlResponse lens_overlay_url_response;
   lens_overlay_url_response.set_url(
       lens::BuildTextOnlySearchURL(query_text, page_url_, page_title_,
-                                   additional_search_query_params)
+                                   additional_search_query_params,
+                                   invocation_source_)
           .spec());
   base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce(url_callback_, lens_overlay_url_response));
@@ -543,9 +546,9 @@
   // Generate and send the Lens search url.
   lens::proto::LensOverlayUrlResponse lens_overlay_url_response;
   lens_overlay_url_response.set_url(
-      lens::BuildLensSearchURL(query_text,
-                               request_id_generator_->GetNextRequestId(),
-                               cluster_info, additional_search_query_params)
+      lens::BuildLensSearchURL(
+          query_text, request_id_generator_->GetNextRequestId(), cluster_info,
+          additional_search_query_params, invocation_source_)
           .spec());
   base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce(url_callback_, lens_overlay_url_response));
diff --git a/chrome/browser/ui/lens/lens_overlay_query_controller.h b/chrome/browser/ui/lens/lens_overlay_query_controller.h
index d84d95a..f708317 100644
--- a/chrome/browser/ui/lens/lens_overlay_query_controller.h
+++ b/chrome/browser/ui/lens/lens_overlay_query_controller.h
@@ -8,6 +8,7 @@
 #include "base/functional/callback.h"
 #include "chrome/browser/lens/core/mojom/overlay_object.mojom.h"
 #include "chrome/browser/lens/core/mojom/text.mojom.h"
+#include "chrome/browser/ui/lens/lens_overlay_invocation_source.h"
 #include "chrome/browser/ui/lens/lens_overlay_request_id_generator.h"
 #include "components/endpoint_fetcher/endpoint_fetcher.h"
 #include "components/lens/proto/server/lens_overlay_response.pb.h"
@@ -54,7 +55,8 @@
       LensOverlayInteractionResponseCallback interaction_data_callback,
       LensOverlayThumbnailCreatedCallback thumbnail_created_callback,
       variations::VariationsClient* variations_client,
-      signin::IdentityManager* identity_manager);
+      signin::IdentityManager* identity_manager,
+      lens::LensOverlayInvocationSource invocation_source);
   virtual ~LensOverlayQueryController();
 
   // Starts a query flow by sending a request to Lens using the screenshot,
@@ -112,7 +114,8 @@
 
  private:
   enum class QueryControllerState {
-    // StartQueryFlow has not been called and the query controller is inactive.
+    // StartQueryFlow has not been called and the query controller is
+    // inactive.
     kOff = 0,
     // The full image response has not been received, or is no longer valid.
     kAwaitingFullImageResponse = 1,
@@ -164,8 +167,8 @@
   // Helper to gate interaction fetches on whether or not the cluster
   // info has been received. If it has not been received, this function
   // sets the cluster info received callback to fetch the interaction.
-  // Additionally, invokes `thumbnail_created_callback_` and passes the data in
-  // `image_crop`.
+  // Additionally, invokes `thumbnail_created_callback_` and passes the data
+  // in `image_crop`.
   void FetchInteractionRequestAndGenerateUrlIfClusterInfoReady(
       int request_index,
       lens::mojom::CenterRotatedBoxPtr region,
@@ -261,13 +264,17 @@
   // incognito profiles.
   raw_ptr<signin::IdentityManager> identity_manager_;
 
-  // The request counter, used to make sure requests are not sent out of order.
+  // The request counter, used to make sure requests are not sent out of
+  // order.
   int request_counter_ = 0;
 
   // Whether or not the parent interaction query has been sent. This should
   // always be the first interaction in a query flow.
   bool parent_query_sent_ = false;
 
+  // The invocation source that triggered the query flow.
+  lens::LensOverlayInvocationSource invocation_source_;
+
   base::WeakPtrFactory<LensOverlayQueryController> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ui/lens/lens_overlay_query_controller_unittest.cc b/chrome/browser/ui/lens/lens_overlay_query_controller_unittest.cc
index b92f29d..6cb0ff9 100644
--- a/chrome/browser/ui/lens/lens_overlay_query_controller_unittest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_query_controller_unittest.cc
@@ -102,13 +102,15 @@
       base::RepeatingCallback<void(const std::string&)>
           thumbnail_created_callback,
       variations::VariationsClient* variations_client,
-      signin::IdentityManager* identity_manager)
+      signin::IdentityManager* identity_manager,
+      lens::LensOverlayInvocationSource invocation_source)
       : LensOverlayQueryController(full_image_callback,
                                    url_callback,
                                    interaction_data_callback,
                                    thumbnail_created_callback,
                                    variations_client,
-                                   identity_manager) {}
+                                   identity_manager,
+                                   invocation_source) {}
   ~LensOverlayQueryControllerMock() override = default;
 
   lens::LensOverlayObjectsResponse fake_objects_response_;
@@ -206,7 +208,8 @@
       full_image_response_future.GetRepeatingCallback(), base::NullCallback(),
       base::NullCallback(), base::NullCallback(),
       profile()->GetVariationsClient(),
-      IdentityManagerFactory::GetForProfile(profile()));
+      IdentityManagerFactory::GetForProfile(profile()),
+      lens::LensOverlayInvocationSource::kAppMenu);
   SkBitmap bitmap = CreateNonEmptyBitmap(100, 100);
   query_controller.StartQueryFlow(
       bitmap, std::make_optional<GURL>(kTestPageUrl),
@@ -261,7 +264,8 @@
       interaction_data_response_future.GetRepeatingCallback(),
       thumbnail_created_future.GetRepeatingCallback(),
       profile()->GetVariationsClient(),
-      IdentityManagerFactory::GetForProfile(profile()));
+      IdentityManagerFactory::GetForProfile(profile()),
+      lens::LensOverlayInvocationSource::kAppMenu);
   query_controller.fake_objects_response_.mutable_cluster_info()
       ->set_server_session_id(kTestServerSessionId);
   query_controller.fake_interaction_response_.set_encoded_response(
@@ -360,7 +364,8 @@
       interaction_data_response_future.GetRepeatingCallback(),
       thumbnail_created_future.GetRepeatingCallback(),
       profile()->GetVariationsClient(),
-      IdentityManagerFactory::GetForProfile(profile()));
+      IdentityManagerFactory::GetForProfile(profile()),
+      lens::LensOverlayInvocationSource::kAppMenu);
   query_controller.fake_objects_response_.mutable_cluster_info()
       ->set_server_session_id(kTestServerSessionId);
   query_controller.fake_interaction_response_.set_encoded_response(
@@ -463,7 +468,8 @@
       interaction_data_response_future.GetRepeatingCallback(),
       thumbnail_created_future.GetRepeatingCallback(),
       profile()->GetVariationsClient(),
-      IdentityManagerFactory::GetForProfile(profile()));
+      IdentityManagerFactory::GetForProfile(profile()),
+      lens::LensOverlayInvocationSource::kAppMenu);
   query_controller.fake_objects_response_.mutable_cluster_info()
       ->set_server_session_id(kTestServerSessionId);
   query_controller.fake_interaction_response_.set_encoded_response(
@@ -547,7 +553,8 @@
       interaction_data_response_future.GetRepeatingCallback(),
       thumbnail_created_future.GetRepeatingCallback(),
       profile()->GetVariationsClient(),
-      IdentityManagerFactory::GetForProfile(profile()));
+      IdentityManagerFactory::GetForProfile(profile()),
+      lens::LensOverlayInvocationSource::kAppMenu);
   SkBitmap bitmap = CreateNonEmptyBitmap(100, 100);
   std::map<std::string, std::string> additional_search_query_params;
   query_controller.StartQueryFlow(
@@ -593,7 +600,8 @@
       interaction_data_response_future.GetRepeatingCallback(),
       thumbnail_created_future.GetRepeatingCallback(),
       profile()->GetVariationsClient(),
-      IdentityManagerFactory::GetForProfile(profile()));
+      IdentityManagerFactory::GetForProfile(profile()),
+      lens::LensOverlayInvocationSource::kAppMenu);
   query_controller.fake_objects_response_.mutable_cluster_info()
       ->set_server_session_id(kTestServerSessionId);
   query_controller.fake_interaction_response_.set_encoded_response(
diff --git a/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc b/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc
index 171d7cf..ce31730 100644
--- a/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc
+++ b/chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.cc
@@ -8,6 +8,8 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/lens/lens_overlay_controller.h"
+#include "chrome/browser/ui/lens/lens_overlay_dismissal_source.h"
+#include "chrome/browser/ui/lens/lens_overlay_invocation_source.h"
 #include "chrome/browser/ui/lens/lens_overlay_url_builder.h"
 #include "chrome/browser/ui/lens/lens_untrusted_ui.h"
 #include "chrome/browser/ui/side_panel/side_panel_ui.h"
@@ -86,10 +88,9 @@
         // Toggle the Lens overlay. There's no need to show or hide the side
         // panel as the overlay controller will handle that.
         if (controller->IsOverlayShowing()) {
-          controller->CloseUIAsync(
-              LensOverlayController::DismissalSource::kToolbar);
+          controller->CloseUIAsync(lens::LensOverlayDismissalSource::kToolbar);
         } else {
-          controller->ShowUI(LensOverlayController::InvocationSource::kToolbar);
+          controller->ShowUI(lens::LensOverlayInvocationSource::kToolbar);
         }
       },
       browser);
diff --git a/chrome/browser/ui/lens/lens_overlay_url_builder.cc b/chrome/browser/ui/lens/lens_overlay_url_builder.cc
index 043cb1b2..503ae473 100644
--- a/chrome/browser/ui/lens/lens_overlay_url_builder.cc
+++ b/chrome/browser/ui/lens/lens_overlay_url_builder.cc
@@ -56,6 +56,21 @@
 inline constexpr char kTliteTargetLanguageParameterKey[] = "tlitetl";
 inline constexpr char kTliteQueryParameterKey[] = "tlitetxt";
 
+// Query parameter for the invocation source.
+inline constexpr char kInvocationSourceParameterKey[] = "source";
+inline constexpr char kInvocationSourceAppMenu[] = "chrome.cr.menu";
+inline constexpr char kInvocationSourcePageSearchContextMenu[] =
+    "chrome.cr.ctxp";
+inline constexpr char kInvocationSourceImageSearchContextMenu[] =
+    "chrome.cr.ctxi";
+inline constexpr char kInvocationSourceFindInPage[] = "chrome.cr.find";
+inline constexpr char kInvocationSourceToolbarIcon[] = "chrome.cr.tbic";
+inline constexpr char kInvocationSourceOmniboxIcon[] = "chrome.cr.obic";
+
+// The url query param for the viewport width and height.
+constexpr char kViewportWidthQueryParamKey[] = "biw";
+constexpr char kViewportHeightQueryParamKey[] = "bih";
+
 // Appends the url params from the map to the url.
 GURL AppendUrlParamsFromMap(
     const GURL& url_to_modify,
@@ -121,13 +136,44 @@
   return new_url;
 }
 
+GURL AppendInvocationSourceParamToURL(
+    const GURL& url_to_modify,
+    lens::LensOverlayInvocationSource invocation_source) {
+  std::string param_value = "";
+  switch (invocation_source) {
+    case lens::LensOverlayInvocationSource::kAppMenu:
+      param_value = kInvocationSourceAppMenu;
+      break;
+    case lens::LensOverlayInvocationSource::kContentAreaContextMenuPage:
+      param_value = kInvocationSourcePageSearchContextMenu;
+      break;
+    case lens::LensOverlayInvocationSource::kContentAreaContextMenuImage:
+      param_value = kInvocationSourceImageSearchContextMenu;
+      break;
+    case lens::LensOverlayInvocationSource::kToolbar:
+      param_value = kInvocationSourceToolbarIcon;
+      break;
+    case lens::LensOverlayInvocationSource::kFindInPage:
+      param_value = kInvocationSourceFindInPage;
+      break;
+    case lens::LensOverlayInvocationSource::kOmnibox:
+      param_value = kInvocationSourceOmniboxIcon;
+      break;
+  }
+  return net::AppendOrReplaceQueryParameter(
+      url_to_modify, kInvocationSourceParameterKey, param_value);
+}
+
 GURL BuildTextOnlySearchURL(
     const std::string& text_query,
     std::optional<GURL> page_url,
     std::optional<std::string> page_title,
-    std::map<std::string, std::string> additional_search_query_params) {
+    std::map<std::string, std::string> additional_search_query_params,
+    lens::LensOverlayInvocationSource invocation_source) {
   GURL url_with_query_params =
       GURL(lens::features::GetLensOverlayResultsSearchURL());
+  url_with_query_params = AppendInvocationSourceParamToURL(
+      url_with_query_params, invocation_source);
   url_with_query_params = AppendUrlParamsFromMap(
       url_with_query_params, additional_search_query_params);
   url_with_query_params = net::AppendOrReplaceQueryParameter(
@@ -146,9 +192,12 @@
     std::optional<std::string> text_query,
     std::unique_ptr<lens::LensOverlayRequestId> request_id,
     lens::LensOverlayClusterInfo cluster_info,
-    std::map<std::string, std::string> additional_search_query_params) {
+    std::map<std::string, std::string> additional_search_query_params,
+    lens::LensOverlayInvocationSource invocation_source) {
   GURL url_with_query_params =
       GURL(lens::features::GetLensOverlayResultsSearchURL());
+  url_with_query_params = AppendInvocationSourceParamToURL(
+      url_with_query_params, invocation_source);
   url_with_query_params = AppendUrlParamsFromMap(
       url_with_query_params, additional_search_query_params);
   url_with_query_params =
@@ -209,4 +258,23 @@
              net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
 }
 
+GURL RemoveUrlViewportParams(const GURL& url) {
+  GURL processed_url = url;
+  std::string actual_viewport_width;
+  bool has_viewport_width = net::GetValueForKeyInQuery(
+      url, kViewportWidthQueryParamKey, &actual_viewport_width);
+  std::string actual_viewport_height;
+  bool has_viewport_height = net::GetValueForKeyInQuery(
+      GURL(url), kViewportHeightQueryParamKey, &actual_viewport_height);
+  if (has_viewport_width) {
+    processed_url = net::AppendOrReplaceQueryParameter(
+        processed_url, kViewportWidthQueryParamKey, std::nullopt);
+  }
+  if (has_viewport_height) {
+    processed_url = net::AppendOrReplaceQueryParameter(
+        processed_url, kViewportHeightQueryParamKey, std::nullopt);
+  }
+  return processed_url;
+}
+
 }  // namespace lens
diff --git a/chrome/browser/ui/lens/lens_overlay_url_builder.h b/chrome/browser/ui/lens/lens_overlay_url_builder.h
index 03b0a73..05854b1 100644
--- a/chrome/browser/ui/lens/lens_overlay_url_builder.h
+++ b/chrome/browser/ui/lens/lens_overlay_url_builder.h
@@ -8,6 +8,7 @@
 #include <optional>
 #include <string>
 
+#include "chrome/browser/ui/lens/lens_overlay_invocation_source.h"
 #include "third_party/lens_server_proto/lens_overlay_cluster_info.pb.h"
 #include "third_party/lens_server_proto/lens_overlay_request_id.pb.h"
 #include "url/gurl.h"
@@ -23,17 +24,23 @@
                                    std::optional<GURL> page_url,
                                    std::optional<std::string> page_title);
 
+GURL AppendInvocationSourceParamToURL(
+    const GURL& url_to_modify,
+    lens::LensOverlayInvocationSource invocation_source);
+
 GURL BuildTextOnlySearchURL(
     const std::string& text_query,
     std::optional<GURL> page_url,
     std::optional<std::string> page_title,
-    std::map<std::string, std::string> additional_search_query_params);
+    std::map<std::string, std::string> additional_search_query_params,
+    lens::LensOverlayInvocationSource invocation_source);
 
 GURL BuildLensSearchURL(
     std::optional<std::string> text_query,
     std::unique_ptr<lens::LensOverlayRequestId> request_id,
     lens::LensOverlayClusterInfo cluster_info,
-    std::map<std::string, std::string> additional_search_query_params);
+    std::map<std::string, std::string> additional_search_query_params,
+    lens::LensOverlayInvocationSource invocation_source);
 
 // Returns the value of the text query parameter value from the provided search
 // URL if any. Empty string otherwise.
@@ -49,6 +56,11 @@
 // finch configured flag.
 bool IsValidSearchResultsUrl(const GURL& url);
 
+// Removes the viewport width (biw) and viewport height (bih) params from the
+// search url. This allows us to compare search url's accurately in
+// AddQueryToHistory when the side panel is resized.
+GURL RemoveUrlViewportParams(const GURL& url);
+
 }  // namespace lens
 
 #endif  // CHROME_BROWSER_UI_LENS_LENS_OVERLAY_URL_BUILDER_H_
diff --git a/chrome/browser/ui/lens/lens_overlay_url_builder_unittest.cc b/chrome/browser/ui/lens/lens_overlay_url_builder_unittest.cc
index 98ee0ab..3393d0a 100644
--- a/chrome/browser/ui/lens/lens_overlay_url_builder_unittest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_url_builder_unittest.cc
@@ -69,14 +69,15 @@
 TEST_F(LensOverlayUrlBuilderTest, BuildTextOnlySearchURL) {
   std::string text_query = "Apples";
   std::map<std::string, std::string> additional_params;
-  std::string expected_url =
-      base::StringPrintf("%s?q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s",
-                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage);
+  std::string expected_url = base::StringPrintf(
+      "%s?source=chrome.cr.menu&q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s",
+      kResultsSearchBaseUrl, text_query.c_str(), kLanguage);
 
-  EXPECT_EQ(lens::BuildTextOnlySearchURL(text_query,
-                                         /*page_url=*/std::nullopt,
-                                         /*page_title=*/std::nullopt,
-                                         additional_params),
+  EXPECT_EQ(lens::BuildTextOnlySearchURL(
+                text_query,
+                /*page_url=*/std::nullopt,
+                /*page_title=*/std::nullopt, additional_params,
+                lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
@@ -96,13 +97,14 @@
       EncodeSearchContext(std::make_optional<GURL>(kPageUrl),
                           std::make_optional<std::string>(kPageTitle));
 
-  std::string expected_url =
-      base::StringPrintf("%s?q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s",
-                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage);
+  std::string expected_url = base::StringPrintf(
+      "%s?source=chrome.cr.menu&q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s",
+      kResultsSearchBaseUrl, text_query.c_str(), kLanguage);
 
   EXPECT_EQ(lens::BuildTextOnlySearchURL(
                 text_query, std::make_optional<GURL>(kPageUrl),
-                std::make_optional<std::string>(kPageTitle), additional_params),
+                std::make_optional<std::string>(kPageTitle), additional_params,
+                lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
@@ -113,14 +115,16 @@
       EncodeSearchContext(std::make_optional<GURL>(kPageUrl),
                           std::make_optional<std::string>(kPageTitle));
 
-  std::string expected_url =
-      base::StringPrintf("%s?q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s&mactx=%s",
-                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
-                         expected_search_context.c_str());
+  std::string expected_url = base::StringPrintf(
+      "%s?source=chrome.cr.menu&q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s&"
+      "mactx=%s",
+      kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
+      expected_search_context.c_str());
 
   EXPECT_EQ(lens::BuildTextOnlySearchURL(
                 text_query, std::make_optional<GURL>(kPageUrl),
-                std::make_optional<std::string>(kPageTitle), additional_params),
+                std::make_optional<std::string>(kPageTitle), additional_params,
+                lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
@@ -130,14 +134,16 @@
   std::string expected_search_context = EncodeSearchContext(
       std::make_optional<GURL>(kPageUrl), /*page_title=*/std::nullopt);
 
-  std::string expected_url =
-      base::StringPrintf("%s?q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s&mactx=%s",
-                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
-                         expected_search_context.c_str());
+  std::string expected_url = base::StringPrintf(
+      "%s?source=chrome.cr.menu&q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s&"
+      "mactx=%s",
+      kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
+      expected_search_context.c_str());
 
   EXPECT_EQ(lens::BuildTextOnlySearchURL(
                 text_query, std::make_optional<GURL>(kPageUrl),
-                /*page_title=*/std::nullopt, additional_params),
+                /*page_title=*/std::nullopt, additional_params,
+                lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
@@ -147,29 +153,32 @@
   std::string expected_search_context = EncodeSearchContext(
       /*page_url=*/std::nullopt, std::make_optional<std::string>(kPageTitle));
 
-  std::string expected_url =
-      base::StringPrintf("%s?q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s&mactx=%s",
-                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
-                         expected_search_context.c_str());
+  std::string expected_url = base::StringPrintf(
+      "%s?source=chrome.cr.menu&q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s&"
+      "mactx=%s",
+      kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
+      expected_search_context.c_str());
 
   EXPECT_EQ(lens::BuildTextOnlySearchURL(
                 text_query,
                 /*page_url=*/std::nullopt,
-                std::make_optional<std::string>(kPageTitle), additional_params),
+                std::make_optional<std::string>(kPageTitle), additional_params,
+                lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
 TEST_F(LensOverlayUrlBuilderTest, BuildTextOnlySearchURLEmpty) {
   std::string text_query = "";
   std::map<std::string, std::string> additional_params;
-  std::string expected_url =
-      base::StringPrintf("%s?q=&lns_mode=text&gsc=1&masfc=c&hl=%s",
-                         kResultsSearchBaseUrl, kLanguage);
+  std::string expected_url = base::StringPrintf(
+      "%s?source=chrome.cr.menu&q=&lns_mode=text&gsc=1&masfc=c&hl=%s",
+      kResultsSearchBaseUrl, kLanguage);
 
-  EXPECT_EQ(lens::BuildTextOnlySearchURL(text_query,
-                                         /*page_url=*/std::nullopt,
-                                         /*page_title=*/std::nullopt,
-                                         additional_params),
+  EXPECT_EQ(lens::BuildTextOnlySearchURL(
+                text_query,
+                /*page_url=*/std::nullopt,
+                /*page_title=*/std::nullopt, additional_params,
+                lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
@@ -179,13 +188,14 @@
   std::string escaped_text_query =
       base::EscapeQueryParamValue(text_query, /*use_plus=*/true);
   std::string expected_url = base::StringPrintf(
-      "%s?q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s", kResultsSearchBaseUrl,
-      escaped_text_query.c_str(), kLanguage);
+      "%s?source=chrome.cr.menu&q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s",
+      kResultsSearchBaseUrl, escaped_text_query.c_str(), kLanguage);
 
-  EXPECT_EQ(lens::BuildTextOnlySearchURL(text_query,
-                                         /*page_url=*/std::nullopt,
-                                         /*page_title=*/std::nullopt,
-                                         additional_params),
+  EXPECT_EQ(lens::BuildTextOnlySearchURL(
+                text_query,
+                /*page_url=*/std::nullopt,
+                /*page_title=*/std::nullopt, additional_params,
+                lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
@@ -195,13 +205,14 @@
   std::string escaped_text_query =
       base::EscapeQueryParamValue(text_query, /*use_plus=*/true);
   std::string expected_url = base::StringPrintf(
-      "%s?q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s", kResultsSearchBaseUrl,
-      escaped_text_query.c_str(), kLanguage);
+      "%s?source=chrome.cr.menu&q=%s&lns_mode=text&gsc=1&masfc=c&hl=%s",
+      kResultsSearchBaseUrl, escaped_text_query.c_str(), kLanguage);
 
-  EXPECT_EQ(lens::BuildTextOnlySearchURL(text_query,
-                                         /*page_url=*/std::nullopt,
-                                         /*page_title=*/std::nullopt,
-                                         additional_params),
+  EXPECT_EQ(lens::BuildTextOnlySearchURL(
+                text_query,
+                /*page_url=*/std::nullopt,
+                /*page_title=*/std::nullopt, additional_params,
+                lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
@@ -222,12 +233,14 @@
   request_id->set_image_sequence_id(image_sequence_id);
 
   std::string expected_url = base::StringPrintf(
-      "%s?gsc=1&masfc=c&hl=%s&q=%s&lns_mode=mu&gsessionid=&udm=24&vsrid=%s",
+      "%s?source=chrome.cr.menu&gsc=1&masfc=c&hl=%s&q=%s&lns_mode=mu&"
+      "gsessionid=&udm=24&vsrid=%s",
       kResultsSearchBaseUrl, kLanguage, escaped_text_query.c_str(),
       EncodeRequestId(request_id.get()).c_str());
 
-  EXPECT_EQ(lens::BuildLensSearchURL(text_query, std::move(request_id),
-                                     cluster_info, additional_params),
+  EXPECT_EQ(lens::BuildLensSearchURL(
+                text_query, std::move(request_id), cluster_info,
+                additional_params, lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
@@ -250,12 +263,14 @@
   request_id->set_image_sequence_id(image_sequence_id);
 
   std::string expected_url = base::StringPrintf(
-      "%s?gsc=1&masfc=c&hl=%s&q=%s&lns_mode=mu&gsessionid=%s&udm=24&vsrid=%s",
+      "%s?source=chrome.cr.menu&gsc=1&masfc=c&hl=%s&q=%s&lns_mode=mu&"
+      "gsessionid=%s&udm=24&vsrid=%s",
       kResultsSearchBaseUrl, kLanguage, escaped_text_query.c_str(),
       search_session_id.c_str(), EncodeRequestId(request_id.get()).c_str());
 
-  EXPECT_EQ(lens::BuildLensSearchURL(text_query, std::move(request_id),
-                                     cluster_info, additional_params),
+  EXPECT_EQ(lens::BuildLensSearchURL(
+                text_query, std::move(request_id), cluster_info,
+                additional_params, lens::LensOverlayInvocationSource::kAppMenu),
             expected_url);
 }
 
@@ -282,14 +297,16 @@
                         &encoded_request_id);
 
   std::string expected_url = base::StringPrintf(
-      "%s?gsc=1&masfc=c&hl=%s&q=&lns_mode=un&gsessionid=%s&udm=26&vsrid=%s",
+      "%s?source=chrome.cr.menu&gsc=1&masfc=c&hl=%s&q=&lns_mode=un&"
+      "gsessionid=%s&udm=26&vsrid=%s",
       kResultsSearchBaseUrl, kLanguage, search_session_id.c_str(),
       encoded_request_id.c_str());
 
-  EXPECT_EQ(lens::BuildLensSearchURL(/*text_query=*/std::nullopt,
-                                     std::move(request_id), cluster_info,
-                                     additional_params),
-            expected_url);
+  EXPECT_EQ(
+      lens::BuildLensSearchURL(
+          /*text_query=*/std::nullopt, std::move(request_id), cluster_info,
+          additional_params, lens::LensOverlayInvocationSource::kAppMenu),
+      expected_url);
 }
 
 TEST_F(LensOverlayUrlBuilderTest, BuildLensSearchURLWithAdditionalParams) {
@@ -315,15 +332,17 @@
                         &encoded_request_id);
 
   std::string expected_url = base::StringPrintf(
-      "%s?param=value&gsc=1&masfc=c&hl=%s&q=&lns_mode=un&gsessionid=%s&udm=26&"
+      "%s?source=chrome.cr.menu&param=value&gsc=1&masfc=c&hl=%s&q=&lns_"
+      "mode=un&gsessionid=%s&udm=26&"
       "vsrid=%s",
       kResultsSearchBaseUrl, kLanguage, search_session_id.c_str(),
       encoded_request_id.c_str());
 
-  EXPECT_EQ(lens::BuildLensSearchURL(/*text_query=*/std::nullopt,
-                                     std::move(request_id), cluster_info,
-                                     additional_params),
-            expected_url);
+  EXPECT_EQ(
+      lens::BuildLensSearchURL(
+          /*text_query=*/std::nullopt, std::move(request_id), cluster_info,
+          additional_params, lens::LensOverlayInvocationSource::kAppMenu),
+      expected_url);
 }
 
 TEST_F(LensOverlayUrlBuilderTest, HasCommonSearchQueryParameters) {
@@ -364,6 +383,50 @@
   EXPECT_FALSE(lens::HasCommonSearchQueryParameters(failing_url6));
 }
 
+TEST_F(LensOverlayUrlBuilderTest,
+       AppendInvocationSourceParamToUrlAppendsEntryPoints) {
+  const GURL base_url(kResultsSearchBaseUrl);
+
+  std::string expected_app_menu_url =
+      base::StringPrintf("%s?source=chrome.cr.menu", kResultsSearchBaseUrl);
+  EXPECT_EQ(lens::AppendInvocationSourceParamToURL(
+                base_url, lens::LensOverlayInvocationSource::kAppMenu),
+            expected_app_menu_url);
+
+  std::string expected_context_menu_page_url =
+      base::StringPrintf("%s?source=chrome.cr.ctxp", kResultsSearchBaseUrl);
+  EXPECT_EQ(lens::AppendInvocationSourceParamToURL(
+                base_url,
+                lens::LensOverlayInvocationSource::kContentAreaContextMenuPage),
+            expected_context_menu_page_url);
+
+  std::string expected_context_menu_image_url =
+      base::StringPrintf("%s?source=chrome.cr.ctxi", kResultsSearchBaseUrl);
+  EXPECT_EQ(
+      lens::AppendInvocationSourceParamToURL(
+          base_url,
+          lens::LensOverlayInvocationSource::kContentAreaContextMenuImage),
+      expected_context_menu_image_url);
+
+  std::string expected_toolbar_url =
+      base::StringPrintf("%s?source=chrome.cr.tbic", kResultsSearchBaseUrl);
+  EXPECT_EQ(lens::AppendInvocationSourceParamToURL(
+                base_url, lens::LensOverlayInvocationSource::kToolbar),
+            expected_toolbar_url);
+
+  std::string expected_find_in_page_url =
+      base::StringPrintf("%s?source=chrome.cr.find", kResultsSearchBaseUrl);
+  EXPECT_EQ(lens::AppendInvocationSourceParamToURL(
+                base_url, lens::LensOverlayInvocationSource::kFindInPage),
+            expected_find_in_page_url);
+
+  std::string expected_omnibox_url =
+      base::StringPrintf("%s?source=chrome.cr.obic", kResultsSearchBaseUrl);
+  EXPECT_EQ(lens::AppendInvocationSourceParamToURL(
+                base_url, lens::LensOverlayInvocationSource::kOmnibox),
+            expected_omnibox_url);
+}
+
 TEST_F(LensOverlayUrlBuilderTest, HasCommonSearchQueryParametersNoQueryParams) {
   const GURL url(kResultsSearchBaseUrl);
   EXPECT_FALSE(lens::HasCommonSearchQueryParameters(url));
@@ -394,4 +457,20 @@
   EXPECT_FALSE(lens::IsValidSearchResultsUrl(GURL()));
 }
 
+TEST_F(LensOverlayUrlBuilderTest, RemoveViewportParamFromURL) {
+  std::string text_query = "Apples";
+  std::string viewport_width = "400";
+  std::string viewport_height = "500";
+  std::string initial_url =
+      base::StringPrintf("%s?q=%s&gsc=1&masfc=c&hl=%s&biw=%s&bih=%s",
+                         kResultsSearchBaseUrl, text_query.c_str(), kLanguage,
+                         viewport_width.c_str(), viewport_height.c_str());
+  std::string expected_url =
+      base::StringPrintf("%s?q=%s&gsc=1&masfc=c&hl=%s", kResultsSearchBaseUrl,
+                         text_query.c_str(), kLanguage);
+
+  EXPECT_EQ(lens::RemoveUrlViewportParams(GURL(initial_url)),
+            GURL(expected_url));
+}
+
 }  // namespace lens
diff --git a/chrome/browser/ui/views/accessibility/accessibility_focus_highlight_browsertest.cc b/chrome/browser/ui/views/accessibility/accessibility_focus_highlight_browsertest.cc
index 8c379ff4..2ef1c722 100644
--- a/chrome/browser/ui/views/accessibility/accessibility_focus_highlight_browsertest.cc
+++ b/chrome/browser/ui/views/accessibility/accessibility_focus_highlight_browsertest.cc
@@ -27,7 +27,6 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/focus_changed_observer.h"
 #include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/accessibility/accessibility_features.h"
 #include "ui/compositor/layer.h"
 #include "ui/gfx/image/image.h"
 #include "ui/snapshot/snapshot.h"
@@ -58,8 +57,6 @@
   // InProcessBrowserTest overrides:
   void SetUp() override {
     EnablePixelOutput();
-    scoped_feature_list_.InitAndEnableFeature(
-        features::kAccessibilityFocusHighlight);
     InProcessBrowserTest::SetUp();
   }
 
@@ -125,9 +122,6 @@
       return result_image;
     }
   }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Smoke test that ensures that when a node gets focus, the layer with the
diff --git a/chrome/browser/ui/views/find_bar_view.cc b/chrome/browser/ui/views/find_bar_view.cc
index d37021f9..4665bb6 100644
--- a/chrome/browser/ui/views/find_bar_view.cc
+++ b/chrome/browser/ui/views/find_bar_view.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ui/find_bar/find_bar_state.h"
 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
 #include "chrome/browser/ui/lens/lens_overlay_controller.h"
+#include "chrome/browser/ui/lens/lens_overlay_invocation_source.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/find_bar_host.h"
@@ -305,7 +306,7 @@
                   CHECK(controller);
 
                   controller->ShowUI(
-                      LensOverlayController::InvocationSource::kFindInPage);
+                      lens::LensOverlayInvocationSource::kFindInPage);
                   UserEducationService::MaybeNotifyPromoFeatureUsed(
                       web_contents->GetBrowserContext(),
                       lens::features::kLensOverlay);
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc
index 3df8d9e..5d7494c 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc
@@ -142,7 +142,10 @@
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   frame->GetNativeWindow()->SetEventTargeter(
-      std::make_unique<chromeos::InteriorResizeHandleTargeter>());
+      std::make_unique<chromeos::InteriorResizeHandleTargeter>(
+          base::BindRepeating([](const aura::Window* window) {
+            return window->GetProperty(chromeos::kWindowStateTypeKey);
+          })));
 #endif
 
   // TODO: b/330360595 - Confirm if this is needed in Lacros.
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc
index 00039ad..040bfc5b 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos_browsertest.cc
@@ -215,6 +215,41 @@
   EXPECT_EQ(HTCLIENT, frame_view->NonClientHitTest(top_edge));
 }
 
+// Regression test for crbug.com/40945061. Asserts that the content window
+// accepts input from the edge of the browser frame when the browser is
+// maximized.
+IN_PROC_BROWSER_TEST_P(BrowserNonClientFrameViewChromeOSTestNoWebUiTabStrip,
+                       ContentWindowAcceptsEdgeInputsWhenMaximized) {
+  BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
+  content::WebContents* web_contents = browser_view->GetActiveWebContents();
+  views::Widget* widget = browser_view->GetWidget();
+  const BrowserNonClientFrameViewChromeOS* frame_view =
+      GetFrameViewChromeOS(browser_view);
+
+  // Maximize the widget.
+  EXPECT_FALSE(widget->IsMaximized());
+  const gfx::Rect old_bounds = frame_view->bounds();
+  widget->Maximize();
+  auto* window = widget->GetNativeWindow();
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return window->GetProperty(chromeos::kWindowStateTypeKey) ==
+           chromeos::WindowStateType::kMaximized;
+  }));
+  // TODO(crbug.com/40276379): Remove waiting for bounds change when the bug
+  // is fixed.
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return frame_view->bounds() != old_bounds; }));
+
+  // Assert that input events at the edge of the browser are propagated to the
+  // web contents window.
+  EXPECT_FALSE(web_contents->GetFocusedFrame());
+  ui::test::EventGenerator event_generator(window->GetRootWindow());
+  ASSERT_NO_FATAL_FAILURE(
+      event_generator.GestureTapAt(frame_view->bounds().left_center()));
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return !!web_contents->GetFocusedFrame(); }));
+}
+
 using BrowserNonClientFrameViewChromeOSTouchTest =
     TopChromeTouchTest<ChromeOSBrowserUITest>;
 
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index e4ebca4..e2cec03 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -234,7 +234,6 @@
 #include "extensions/common/command.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
-#include "ui/accessibility/accessibility_features.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_mode_observer.h"
 #include "ui/accessibility/ax_node_data.h"
@@ -1343,8 +1342,7 @@
   }
 
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
-  if (features::IsAccessibilityFocusHighlightEnabled() &&
-      !accessibility_focus_highlight_) {
+  if (!accessibility_focus_highlight_) {
     accessibility_focus_highlight_ =
         std::make_unique<AccessibilityFocusHighlight>(this);
   }
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
index 636be12..2c97135 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
@@ -67,6 +67,8 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/ui/base/window_properties.h"
+#include "chromeos/ui/base/window_state_type.h"
 #include "chromeos/ui/frame/interior_resize_handler_targeter.h"
 #endif
 
@@ -602,7 +604,10 @@
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   frame->GetNativeWindow()->SetEventTargeter(
-      std::make_unique<chromeos::InteriorResizeHandleTargeter>());
+      std::make_unique<chromeos::InteriorResizeHandleTargeter>(
+          base::BindRepeating([](const aura::Window* window) {
+            return window->GetProperty(chromeos::kWindowStateTypeKey);
+          })));
 #endif
 }
 
diff --git a/chrome/browser/ui/views/mahi/BUILD.gn b/chrome/browser/ui/views/mahi/BUILD.gn
index fbe5080..44743f2e 100644
--- a/chrome/browser/ui/views/mahi/BUILD.gn
+++ b/chrome/browser/ui/views/mahi/BUILD.gn
@@ -20,10 +20,12 @@
   deps = [
     "//base",
     "//chrome/browser/chromeos",
+    "//chrome/browser/ui/chromeos/magic_boost",
     "//chrome/browser/ui/chromeos/read_write_cards",
     "//chrome/browser/ui/views/editor_menu:utils",
     "//chromeos/components/editor_menu/public/cpp",
     "//chromeos/components/mahi/public/cpp",
+    "//chromeos/constants:constants",
     "//chromeos/strings:strings_grit",
     "//chromeos/ui/vector_icons",
     "//ui/color",
diff --git a/chrome/browser/ui/views/mahi/mahi_menu_controller.cc b/chrome/browser/ui/views/mahi/mahi_menu_controller.cc
index b17b0cb..0704047a 100644
--- a/chrome/browser/ui/views/mahi/mahi_menu_controller.cc
+++ b/chrome/browser/ui/views/mahi/mahi_menu_controller.cc
@@ -9,12 +9,14 @@
 #include "base/command_line.h"
 #include "base/metrics/histogram_functions.h"
 #include "chrome/browser/chromeos/mahi/mahi_web_contents_manager.h"
+#include "chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.h"
 #include "chrome/browser/ui/chromeos/read_write_cards/read_write_cards_ui_controller.h"
 #include "chrome/browser/ui/views/mahi/mahi_condensed_menu_view.h"
 #include "chrome/browser/ui/views/mahi/mahi_menu_constants.h"
 #include "chrome/browser/ui/views/mahi/mahi_menu_view.h"
 #include "chromeos/components/mahi/public/cpp/mahi_manager.h"
 #include "chromeos/components/mahi/public/cpp/mahi_switches.h"
+#include "chromeos/constants/chromeos_features.h"
 #include "ui/views/view_utils.h"
 
 namespace chromeos::mahi {
@@ -50,6 +52,12 @@
     return;
   }
 
+  if (features::IsMagicBoostEnabled() &&
+      MagicBoostController::Get()->ShouldQuickAnswersAndMahiShowOptIn()) {
+    MagicBoostController::Get()->ShowOptInUi();
+    return;
+  }
+
   if (selected_text.empty()) {
     menu_widget_ = MahiMenuView::CreateWidget(anchor_bounds);
     menu_widget_->ShowInactive();
@@ -76,6 +84,10 @@
     menu_widget_.reset();
   }
 
+  if (features::IsMagicBoostEnabled()) {
+    MagicBoostController::Get()->CloseOptInUi();
+  }
+
   read_write_cards_ui_controller_->RemoveMahiUi();
 }
 
diff --git a/chrome/browser/ui/views/mahi/mahi_menu_controller_unittest.cc b/chrome/browser/ui/views/mahi/mahi_menu_controller_unittest.cc
index 78f5bdf..3dee4ec5 100644
--- a/chrome/browser/ui/views/mahi/mahi_menu_controller_unittest.cc
+++ b/chrome/browser/ui/views/mahi/mahi_menu_controller_unittest.cc
@@ -13,6 +13,8 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/chromeos/mahi/test/fake_mahi_web_contents_manager.h"
 #include "chrome/browser/chromeos/mahi/test/scoped_mahi_web_contents_manager_for_testing.h"
+#include "chrome/browser/ui/chromeos/magic_boost/magic_boost_controller.h"
+#include "chrome/browser/ui/chromeos/magic_boost/test/mock_magic_boost_controller.h"
 #include "chrome/browser/ui/chromeos/read_write_cards/read_write_cards_ui_controller.h"
 #include "chrome/browser/ui/views/editor_menu/utils/utils.h"
 #include "chrome/browser/ui/views/mahi/mahi_condensed_menu_view.h"
@@ -35,10 +37,28 @@
 namespace chromeos::mahi {
 
 using ::testing::IsNull;
+using ::testing::Mock;
+using ::testing::NiceMock;
+using ::testing::Return;
 
-class MahiMenuControllerTest : public ChromeViewsTestBase {
+class MahiMenuControllerTest : public ChromeViewsTestBase,
+                               public testing::WithParamInterface<bool> {
  public:
   MahiMenuControllerTest() {
+    if (IsMagicBoostEnabled()) {
+      feature_list_.InitWithFeatures(
+          /*enabled_features=*/{features::kMahi, features::kMagicBoost},
+          /*disabled_features=*/{});
+
+      scoped_magic_boost_controller_ =
+          std::make_unique<ScopedMagicBoostControllerForTesting>(
+              &mock_magic_boost_controller_);
+    } else {
+      feature_list_.InitWithFeatures(
+          /*enabled_features=*/{features::kMahi},
+          /*disabled_features=*/{features::kMagicBoost});
+    }
+
     menu_controller_ =
         std::make_unique<MahiMenuController>(read_write_cards_ui_controller_);
 
@@ -52,6 +72,8 @@
     ChangePrefValue(true);
   }
 
+  bool IsMagicBoostEnabled() const { return GetParam(); }
+
   MahiMenuControllerTest(const MahiMenuControllerTest&) = delete;
   MahiMenuControllerTest& operator=(const MahiMenuControllerTest&) = delete;
 
@@ -59,6 +81,10 @@
 
   MahiMenuController* menu_controller() { return menu_controller_.get(); }
 
+  MockMagicBoostController& mock_magic_boost_controller() {
+    return mock_magic_boost_controller_;
+  }
+
   void ChangePageDistillability(bool value) {
     fake_mahi_web_contents_manager_.set_focused_web_content_is_distillable(
         value);
@@ -72,22 +98,30 @@
   ReadWriteCardsUiController read_write_cards_ui_controller_;
 
  private:
+  base::test::ScopedFeatureList feature_list_;
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  base::AutoReset<bool> ignore_mahi_secret_key_ =
+      ash::switches::SetIgnoreMahiSecretKeyForTest();
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   std::unique_ptr<MahiMenuController> menu_controller_;
 
   ::mahi::FakeMahiWebContentsManager fake_mahi_web_contents_manager_;
   std::unique_ptr<::mahi::ScopedMahiWebContentsManagerForTesting>
       scoped_mahi_web_contents_manager_;
 
-  base::test::ScopedFeatureList feature_list_{chromeos::features::kMahi};
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  base::AutoReset<bool> ignore_mahi_secret_key_ =
-      ash::switches::SetIgnoreMahiSecretKeyForTest();
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+  NiceMock<MockMagicBoostController> mock_magic_boost_controller_;
+  std::unique_ptr<ScopedMagicBoostControllerForTesting>
+      scoped_magic_boost_controller_;
 };
 
 // Tests the behavior of the controller when there's no text selected when
 // `OnTextAvailable()` is triggered.
-TEST_F(MahiMenuControllerTest, TextNotSelected) {
+TEST_P(MahiMenuControllerTest, TextNotSelected) {
+  ON_CALL(mock_magic_boost_controller(), ShouldQuickAnswersAndMahiShowOptIn)
+      .WillByDefault(Return(false));
+
   EXPECT_FALSE(menu_controller()->menu_widget_for_test());
 
   // Menu widget should show when text is displayed.
@@ -115,7 +149,7 @@
 
 // Tests the behavior of the controller when `OnAnchorBoundsChanged()` is
 // triggered.
-TEST_F(MahiMenuControllerTest, BoundsChanged) {
+TEST_P(MahiMenuControllerTest, BoundsChanged) {
   EXPECT_FALSE(menu_controller()->menu_widget_for_test());
 
   gfx::Rect anchor_bounds = gfx::Rect(50, 50, 25, 100);
@@ -140,7 +174,10 @@
 
 // Tests the behavior of the controller when there's text selected when
 // `OnTextAvailable()` is triggered.
-TEST_F(MahiMenuControllerTest, TextSelected) {
+TEST_P(MahiMenuControllerTest, TextSelected) {
+  ON_CALL(mock_magic_boost_controller(), ShouldQuickAnswersAndMahiShowOptIn)
+      .WillByDefault(Return(false));
+
   EXPECT_FALSE(read_write_cards_ui_controller_.widget_for_test());
 
   // Menu widget should show when text is displayed.
@@ -160,8 +197,75 @@
   EXPECT_FALSE(read_write_cards_ui_controller_.GetMahiUiForTest());
 }
 
+TEST_P(MahiMenuControllerTest, ShowOptInUiTextNotSelected) {
+  ON_CALL(mock_magic_boost_controller(), ShouldQuickAnswersAndMahiShowOptIn)
+      .WillByDefault(Return(true));
+
+  // `ShowOptInUi` should be called when Magic Boost is enabled.
+  if (IsMagicBoostEnabled()) {
+    EXPECT_CALL(mock_magic_boost_controller(), ShowOptInUi);
+    menu_controller()->OnTextAvailable(/*anchor_bounds=*/gfx::Rect(),
+                                       /*selected_text=*/"",
+                                       /*surrounding_text=*/"");
+
+    EXPECT_CALL(mock_magic_boost_controller(), CloseOptInUi);
+    menu_controller()->OnDismiss(/*is_other_command_executed=*/false);
+
+    Mock::VerifyAndClear(&mock_magic_boost_controller());
+    return;
+  }
+
+  // Otherwise, no opt in UI is shown and `MahiMenuView` is shown.
+  EXPECT_CALL(mock_magic_boost_controller(), ShowOptInUi).Times(0);
+  menu_controller()->OnTextAvailable(/*anchor_bounds=*/gfx::Rect(),
+                                     /*selected_text=*/"",
+                                     /*surrounding_text=*/"");
+
+  EXPECT_TRUE(menu_controller()->menu_widget_for_test());
+  EXPECT_TRUE(menu_controller()->menu_widget_for_test()->IsVisible());
+  EXPECT_TRUE(views::IsViewClass<MahiMenuView>(
+      menu_controller()->menu_widget_for_test()->GetContentsView()));
+
+  EXPECT_CALL(mock_magic_boost_controller(), CloseOptInUi).Times(0);
+  menu_controller()->OnDismiss(/*is_other_command_executed=*/false);
+}
+
+TEST_P(MahiMenuControllerTest, ShowOptInUiTextSelected) {
+  ON_CALL(mock_magic_boost_controller(), ShouldQuickAnswersAndMahiShowOptIn)
+      .WillByDefault(Return(true));
+
+  // `ShowOptInUi` should be called when Magic Boost is enabled.
+  if (IsMagicBoostEnabled()) {
+    EXPECT_CALL(mock_magic_boost_controller(), ShowOptInUi);
+    menu_controller()->OnTextAvailable(/*anchor_bounds=*/gfx::Rect(),
+                                       /*selected_text=*/"test selected text",
+                                       /*surrounding_text=*/"");
+
+    EXPECT_CALL(mock_magic_boost_controller(), CloseOptInUi);
+    menu_controller()->OnDismiss(/*is_other_command_executed=*/false);
+
+    Mock::VerifyAndClear(&mock_magic_boost_controller());
+    return;
+  }
+
+  // Otherwise, no opt in UI is shown and the condense menu view is shown.
+  EXPECT_CALL(mock_magic_boost_controller(), ShowOptInUi).Times(0);
+  menu_controller()->OnTextAvailable(/*anchor_bounds=*/gfx::Rect(),
+                                     /*selected_text=*/"test selected text",
+                                     /*surrounding_text=*/"");
+
+  EXPECT_TRUE(read_write_cards_ui_controller_.widget_for_test());
+  EXPECT_TRUE(read_write_cards_ui_controller_.widget_for_test()->IsVisible());
+  EXPECT_TRUE(read_write_cards_ui_controller_.GetMahiUiForTest());
+  EXPECT_TRUE(views::IsViewClass<MahiCondensedMenuView>(
+      read_write_cards_ui_controller_.GetMahiUiForTest()));
+
+  EXPECT_CALL(mock_magic_boost_controller(), CloseOptInUi).Times(0);
+  menu_controller()->OnDismiss(/*is_other_command_executed=*/false);
+}
+
 // Tests the behavior of the controller when pref state changed.
-TEST_F(MahiMenuControllerTest, PrefChange) {
+TEST_P(MahiMenuControllerTest, PrefChange) {
   EXPECT_FALSE(menu_controller()->menu_widget_for_test());
 
   // Menu widget should show when text is displayed as the default is that Mahi
@@ -197,7 +301,7 @@
       menu_controller()->menu_widget_for_test()->GetContentsView()));
 }
 
-TEST_F(MahiMenuControllerTest, DistillableMetrics) {
+TEST_P(MahiMenuControllerTest, DistillableMetrics) {
   base::HistogramTester histogram_tester;
 
   histogram_tester.ExpectBucketCount(kMahiContextMenuDistillableHistogram, true,
@@ -227,6 +331,10 @@
                                      false, 1);
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         MahiMenuControllerTest,
+                         /*IsMagicBoostEnabled()=*/testing::Bool());
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 class MahiMenuControllerFeatureKeyTest : public ChromeViewsTestBase {
  public:
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index eeb24d2..431c9ed 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -89,7 +89,6 @@
 #include "ui/base/models/list_selection_model.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/theme_provider.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/color/color_provider.h"
 #include "ui/display/display.h"
 #include "ui/gfx/animation/throb_animation.h"
@@ -1260,7 +1259,7 @@
   // The Tabstrip in the refreshed style does not meet the contrast ratio
   // requirements listed below but does not have strokes for Tabs or the bottom
   // border.
-  if (features::IsChromeRefresh2023() && !using_system_theme) {
+  if (!using_system_theme) {
     return false;
   }
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip_control_button.cc b/chrome/browser/ui/views/tabs/tab_strip_control_button.cc
index 529be298..d0ee903b5 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_control_button.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_control_button.cc
@@ -12,7 +12,6 @@
 #include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
 #include "third_party/skia/include/core/SkPath.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/base/ui_base_features.h"
 #include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/gfx/geometry/skia_conversions.h"
 #include "ui/gfx/vector_icon_types.h"
@@ -93,10 +92,6 @@
   SetImageCentered(true);
   SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
 
-  // By default control buttons in the tab strip should be non-transparent for
-  // the updated Chrome refresh UX.
-  paint_transparent_for_custom_image_theme_ = !features::IsChromeRefresh2023();
-
   foreground_frame_active_color_id_ = kColorTabForegroundInactiveFrameActive;
   foreground_frame_inactive_color_id_ =
       kColorNewTabButtonCRForegroundFrameInactive;
@@ -111,9 +106,7 @@
   SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
 
   views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
-  if (features::IsChromeRefresh2023()) {
-    views::InkDrop::Get(this)->SetLayerRegion(views::LayerRegion::kAbove);
-  }
+  views::InkDrop::Get(this)->SetLayerRegion(views::LayerRegion::kAbove);
   views::HighlightPathGenerator::Install(
       this, std::make_unique<ControlButtonHighlightPathGenerator>(this));
   UpdateInkDrop();
@@ -187,22 +180,8 @@
     return;
   }
 
-  if (features::IsChromeRefresh2023()) {
-    CreateToolbarInkdropCallbacks(this, kColorTabStripControlButtonInkDrop,
-                                  kColorTabStripControlButtonInkDropRipple);
-  } else {
-    const bool frame_active =
-        (GetWidget() && GetWidget()->ShouldPaintAsActive());
-
-    // These values are also used in refresh by
-    // `kColorTabStripControlButtonInkDrop` and
-    // `kColorTabStripControlButtonInkDropRipple` in case of themes.
-    views::InkDrop::Get(this)->SetHighlightOpacity(0.16f);
-    views::InkDrop::Get(this)->SetVisibleOpacity(0.14f);
-    views::InkDrop::Get(this)->SetBaseColor(color_provider->GetColor(
-        frame_active ? kColorNewTabButtonInkDropFrameActive
-                     : kColorNewTabButtonInkDropFrameInactive));
-  }
+  CreateToolbarInkdropCallbacks(this, kColorTabStripControlButtonInkDrop,
+                                kColorTabStripControlButtonInkDropRipple);
 }
 
 void TabStripControlButton::UpdateColors() {
diff --git a/chrome/browser/ui/views/tabs/tab_strip_control_button.h b/chrome/browser/ui/views/tabs/tab_strip_control_button.h
index fbb2a668..c12fb1d3 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_control_button.h
+++ b/chrome/browser/ui/views/tabs/tab_strip_control_button.h
@@ -113,7 +113,7 @@
   // Optional icon for the label button.
   raw_ref<const gfx::VectorIcon> icon_;
 
-  bool paint_transparent_for_custom_image_theme_;
+  bool paint_transparent_for_custom_image_theme_ = false;
 
   // Button edge which should render without rounded corners.
   Edge flat_edge_;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
index abc9fb1..07194e26a 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
@@ -589,11 +589,9 @@
   EXPECT_TRUE(second_tab->closing());
 }
 
-TEST_P(TabStripTest, ChangingLayoutTypeResizesTabs) {
-  // TODO (crbug/1520595): Skip for now due to test failing when CR2023 enabled.
-  if (features::IsChromeRefresh2023()) {
-    GTEST_SKIP();
-  }
+// TODO (crbug.com/1520595): Disabled for now due to test failing when CR2023
+// enabled.
+TEST_P(TabStripTest, DISABLED_ChangingLayoutTypeResizesTabs) {
   SetMaxTabStripWidth(1000);
 
   controller_->AddTab(0, TabActive::kInactive);
diff --git a/chrome/browser/ui/views/user_education/browser_user_education_service.cc b/chrome/browser/ui/views/user_education/browser_user_education_service.cc
index 5738862..d8c343819 100644
--- a/chrome/browser/ui/views/user_education/browser_user_education_service.cc
+++ b/chrome/browser/ui/views/user_education/browser_user_education_service.cc
@@ -558,6 +558,40 @@
 
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+  // kIPHExplicitBrowserSigninPreferenceRememberedFeature:
+  registry.RegisterFeature(std::move(
+      FeaturePromoSpecification::CreateForCustomAction(
+          feature_engagement::
+              kIPHExplicitBrowserSigninPreferenceRememberedFeature,
+          kToolbarAvatarButtonElementId,
+          IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CHROME_SIGNIN_IPH_TEXT_SIGNIN,
+          IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CHROME_SIGNIN_IPH_SETTINGS_BUTTON,
+          base::BindRepeating([](ui::ElementContext ctx,
+                                 user_education::FeaturePromoHandle
+                                     promo_handle) {
+            auto* browser = chrome::FindBrowserWithUiElementContext(ctx);
+            if (!browser) {
+              return;
+            }
+            ShowPromoInPage::Params params;
+            params.bubble_anchor_id = kToolbarAvatarButtonElementId;
+            params.bubble_arrow = user_education::HelpBubbleArrow::kTopRight;
+            params.bubble_text = l10n_util::GetStringUTF16(
+                IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CHROME_SIGNIN_IPH_TEXT_SIGNIN);
+            ShowPromoInPage::Start(browser, std::move(params));
+            chrome::ShowSettingsSubPage(browser, chrome::kSyncSetupSubPage);
+            base::RecordAction(
+                base::UserMetricsAction("ExplicitBrowserSigninPreferenceRemembe"
+                                        "red_IPHPromo_SettingsPageOpened"));
+          }))
+          .SetPromoSubtype(user_education::FeaturePromoSpecification::
+                               PromoSubtype::kKeyedNotice)
+          .SetBubbleTitleText(
+              IDS_SIGNIN_DICE_WEB_INTERCEPT_BUBBLE_CHROME_SIGNIN_IPH_TITLE_SIGNIN)
+          .SetCustomActionIsDefault(false)));
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
+
   // kIPHCookieControlsFeature:
   registry.RegisterFeature(std::move(
       FeaturePromoSpecification::CreateForCustomAction(
diff --git a/chrome/browser/ui/webui/ash/in_session_password_change/password_change_ui.cc b/chrome/browser/ui/webui/ash/in_session_password_change/password_change_ui.cc
index 3173ea1a..6f411c7 100644
--- a/chrome/browser/ui/webui/ash/in_session_password_change/password_change_ui.cc
+++ b/chrome/browser/ui/webui/ash/in_session_password_change/password_change_ui.cc
@@ -157,7 +157,8 @@
 
   source->AddResourcePaths(
       base::make_span(kPasswordChangeResources, kPasswordChangeResourcesSize));
-  source->SetDefaultResource(IDR_PASSWORD_CHANGE_CONFIRM_PASSWORD_CHANGE_HTML);
+  source->SetDefaultResource(
+      IDR_PASSWORD_CHANGE_CONFIRM_PASSWORD_CHANGE_APP_HTML);
 
   // The ConfirmPasswordChangeHandler is added by the dialog, so no need to add
   // it here.
diff --git a/chrome/browser/ui/webui/ash/settings/pages/files/files_section.cc b/chrome/browser/ui/webui/ash/settings/pages/files/files_section.cc
index 212bb445..792f252 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/files/files_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/files/files_section.cc
@@ -286,14 +286,12 @@
   if (user && user->GetAccountId().is_valid()) {
     html_source->AddString(
         "googleDriveSignedInAs",
-        l10n_util::GetStringFUTF16(
-            IDS_SETTINGS_GOOGLE_DRIVE_SIGNED_IN_AS,
-            base::ASCIIToUTF16(user->GetAccountId().GetUserEmail())));
+        l10n_util::GetStringFUTF16(IDS_SETTINGS_GOOGLE_DRIVE_SIGNED_IN_AS,
+                                   base::ASCIIToUTF16(user->display_email())));
     html_source->AddString(
         "googleDriveReconnectAs",
-        l10n_util::GetStringFUTF16(
-            IDS_SETTINGS_GOOGLE_DRIVE_RECONNECT_AS,
-            base::ASCIIToUTF16(user->GetAccountId().GetUserEmail())));
+        l10n_util::GetStringFUTF16(IDS_SETTINGS_GOOGLE_DRIVE_RECONNECT_AS,
+                                   base::ASCIIToUTF16(user->display_email())));
   }
 
   html_source->AddBoolean(
diff --git a/chrome/browser/ui/webui/history/browsing_history_handler.cc b/chrome/browser/ui/webui/history/browsing_history_handler.cc
index 471244b..6435cc3 100644
--- a/chrome/browser/ui/webui/history/browsing_history_handler.cc
+++ b/chrome/browser/ui/webui/history/browsing_history_handler.cc
@@ -174,7 +174,8 @@
     UrlIdentity::Type::kDefault, UrlIdentity::Type::kFile,
     UrlIdentity::Type::kIsolatedWebApp, UrlIdentity::Type::kChromeExtension};
 constexpr UrlIdentity::FormatOptions url_identity_options{
-    .default_options = {UrlIdentity::DefaultFormatOptions::kHostname}};
+    .default_options = {UrlIdentity::DefaultFormatOptions::
+                            kOmitSchemePathAndTrivialSubdomains}};
 
 // Converts `entry` to a base::Value::Dict to be owned by the caller.
 base::Value::Dict HistoryEntryToValue(
diff --git a/chrome/browser/ui/webui/management/management_ui.cc b/chrome/browser/ui/webui/management/management_ui.cc
index a32a79ab..716fee30 100644
--- a/chrome/browser/ui/webui/management/management_ui.cc
+++ b/chrome/browser/ui/webui/management/management_ui.cc
@@ -161,6 +161,14 @@
       {kManagementOnPageVisitedVisibleData,
        IDS_MANAGEMENT_PAGE_VISITED_VISIBLE_DATA},
       {kManagementLegacyTechReport, IDS_MANAGEMENT_LEGACY_TECH_REPORT},
+      // Profile reporting messages
+      {kProfileReportingExplanation,
+       IDS_MANAGEMENT_PROFILE_REPORTING_EXPLANATION},
+      {kProfileReportingOverview, IDS_MANAGEMENT_PROFILE_REPORTING_OVERVIEW},
+      {kProfileReportingUsername, IDS_MANAGEMENT_PROFILE_REPORTING_USERNAME},
+      {kProfileReportingBrowser, IDS_MANAGEMENT_PROFILE_REPORTING_BROWSER},
+      {kProfileReportingExtension, IDS_MANAGEMENT_PROFILE_REPORTING_EXTENSION},
+      {kProfileReportingPolicy, IDS_MANAGEMENT_PROFILE_REPORTING_POLICY},
   };
 
   source->AddLocalizedStrings(kLocalizedStrings);
diff --git a/chrome/browser/ui/webui/management/management_ui_constants.cc b/chrome/browser/ui/webui/management/management_ui_constants.cc
index 9c425c7..3b667db 100644
--- a/chrome/browser/ui/webui/management/management_ui_constants.cc
+++ b/chrome/browser/ui/webui/management/management_ui_constants.cc
@@ -71,6 +71,13 @@
 const char kReportingTypeUserActivity[] = "user-activity";
 const char kReportingTypeLegacyTech[] = "legacy-tech";
 
+const char kProfileReportingExplanation[] = "profileReportingExplanation";
+const char kProfileReportingOverview[] = "profileReportingOverview";
+const char kProfileReportingUsername[] = "profileReportingUsername";
+const char kProfileReportingBrowser[] = "profileReportingBrowser";
+const char kProfileReportingExtension[] = "profileReportingExtension";
+const char kProfileReportingPolicy[] = "profileReportingPolicy";
+
 #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
 const char kManagementScreenCaptureEvent[] = "managementScreenCaptureEvent";
 const char kManagementScreenCaptureData[] = "managementScreenCaptureData";
diff --git a/chrome/browser/ui/webui/management/management_ui_constants.h b/chrome/browser/ui/webui/management/management_ui_constants.h
index b072b0d..6e72ee4 100644
--- a/chrome/browser/ui/webui/management/management_ui_constants.h
+++ b/chrome/browser/ui/webui/management/management_ui_constants.h
@@ -92,4 +92,11 @@
 extern const char kReportingTypeUserActivity[];
 extern const char kReportingTypeLegacyTech[];
 
+extern const char kProfileReportingExplanation[];
+extern const char kProfileReportingOverview[];
+extern const char kProfileReportingUsername[];
+extern const char kProfileReportingBrowser[];
+extern const char kProfileReportingExtension[];
+extern const char kProfileReportingPolicy[];
+
 #endif  // CHROME_BROWSER_UI_WEBUI_MANAGEMENT_MANAGEMENT_UI_CONSTANTS_H_
diff --git a/chrome/browser/ui/webui/management/management_ui_handler.cc b/chrome/browser/ui/webui/management/management_ui_handler.cc
index f0168eca..caddde9 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler.cc
+++ b/chrome/browser/ui/webui/management/management_ui_handler.cc
@@ -220,6 +220,10 @@
       "initBrowserReportingInfo",
       base::BindRepeating(&ManagementUIHandler::HandleInitBrowserReportingInfo,
                           base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "initProfileReportingInfo",
+      base::BindRepeating(&ManagementUIHandler::HandleInitProfileReportingInfo,
+                          base::Unretained(this)));
 }
 
 void ManagementUIHandler::OnJavascriptAllowed() {
@@ -230,7 +234,8 @@
   RemoveObservers();
 }
 
-void ManagementUIHandler::AddReportingInfo(base::Value::List* report_sources) {
+void ManagementUIHandler::AddReportingInfo(base::Value::List* report_sources,
+                                           bool is_browser) {
   const policy::PolicyService* policy_service = GetPolicyService();
 
   const policy::PolicyNamespace
@@ -261,6 +266,9 @@
            ->GetPrefs()
            ->GetList(enterprise_reporting::kCloudLegacyTechReportAllowlist)
            .empty();
+  const bool cloud_profile_reporting_policy_enabled =
+      Profile::FromWebUI(web_ui())->GetPrefs()->GetBoolean(
+          enterprise_reporting::kCloudProfileReportingEnabled);
 
   if (cloud_legacy_tech_report_enabled) {
     Profile::FromWebUI(web_ui())->GetPrefs()->GetList(
@@ -293,42 +301,58 @@
       {kPolicyKeyReportUserBrowsingData, kManagementLegacyTechReport,
        ReportingType::kLegacyTech, cloud_legacy_tech_report_enabled}};
 
-  std::unordered_set<const char*> enabled_messages;
-
-  for (auto& report_definition : report_definitions) {
-    if (report_definition.cloud_reporting_enabled) {
-      enabled_messages.insert(report_definition.message);
-    } else if (report_definition.reporting_extension_policy_key) {
-      for (const policy::PolicyMap* policy_map : policy_maps) {
-        const base::Value* policy_value = policy_map->GetValue(
-            report_definition.reporting_extension_policy_key,
-            base::Value::Type::BOOLEAN);
-        if (policy_value && policy_value->GetBool()) {
-          enabled_messages.insert(report_definition.message);
-          break;
+  if (is_browser) {
+    std::unordered_set<const char*> enabled_messages;
+    for (auto& report_definition : report_definitions) {
+      if (report_definition.cloud_reporting_enabled) {
+        enabled_messages.insert(report_definition.message);
+      } else if (report_definition.reporting_extension_policy_key) {
+        for (const policy::PolicyMap* policy_map : policy_maps) {
+          const base::Value* policy_value = policy_map->GetValue(
+              report_definition.reporting_extension_policy_key,
+              base::Value::Type::BOOLEAN);
+          if (policy_value && policy_value->GetBool()) {
+            enabled_messages.insert(report_definition.message);
+            break;
+          }
         }
       }
     }
-  }
 
-  // The message with more data collected for kPolicyKeyReportMachineIdData
-  // trumps the one with less data.
-  if (enabled_messages.find(kManagementExtensionReportMachineNameAddress) !=
-      enabled_messages.end()) {
-    enabled_messages.erase(kManagementExtensionReportMachineName);
-  }
-
-  for (auto& report_definition : report_definitions) {
-    if (enabled_messages.find(report_definition.message) ==
+    // The message with more data collected for kPolicyKeyReportMachineIdData
+    // trumps the one with less data.
+    if (enabled_messages.find(kManagementExtensionReportMachineNameAddress) !=
         enabled_messages.end()) {
-      continue;
+      enabled_messages.erase(kManagementExtensionReportMachineName);
     }
 
-    base::Value::Dict data;
-    data.Set("messageId", report_definition.message);
-    data.Set("reportingType",
-             GetReportingTypeValue(report_definition.reporting_type));
-    report_sources->Append(std::move(data));
+    for (auto& report_definition : report_definitions) {
+      if (enabled_messages.find(report_definition.message) ==
+          enabled_messages.end()) {
+        continue;
+      }
+
+      base::Value::Dict data;
+      data.Set("messageId", report_definition.message);
+      data.Set("reportingType",
+               GetReportingTypeValue(report_definition.reporting_type));
+      report_sources->Append(std::move(data));
+    }
+  } else {
+    if (cloud_reporting_policy_enabled ||
+        !cloud_profile_reporting_policy_enabled) {
+      return;
+    }
+
+    const std::string messages[] = {
+        kProfileReportingOverview, kProfileReportingUsername,
+        kProfileReportingBrowser, kProfileReportingExtension,
+        kProfileReportingPolicy};
+    for (const auto& message : messages) {
+      base::Value::Dict data;
+      data.Set("messageId", message);
+      report_sources->Append(std::move(data));
+    }
   }
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
   // Insert the device signals consent disclosure at the end of browser
@@ -620,16 +644,30 @@
     const base::Value::List& args) {
   base::Value::List report_sources;
   AllowJavascript();
-  AddReportingInfo(&report_sources);
+  AddReportingInfo(&report_sources, /*is_browser=*/true);
+  ResolveJavascriptCallback(args[0] /* callback_id */, report_sources);
+}
+
+void ManagementUIHandler::HandleInitProfileReportingInfo(
+    const base::Value::List& args) {
+  base::Value::List report_sources;
+  AllowJavascript();
+  AddReportingInfo(&report_sources, /*is_browser=*/false);
   ResolveJavascriptCallback(args[0] /* callback_id */, report_sources);
 }
 
 void ManagementUIHandler::NotifyBrowserReportingInfoUpdated() {
   base::Value::List report_sources;
-  AddReportingInfo(&report_sources);
+  AddReportingInfo(&report_sources, /*is_browser=*/true);
   FireWebUIListener("browser-reporting-info-updated", report_sources);
 }
 
+void ManagementUIHandler::NotifyProfileReportingInfoUpdated() {
+  base::Value::List report_sources;
+  AddReportingInfo(&report_sources, /*is_browser=*/false);
+  FireWebUIListener("profile-reporting-info-updated", report_sources);
+}
+
 void ManagementUIHandler::NotifyThreatProtectionInfoUpdated() {
   FireWebUIListener("threat-protection-info-updated",
                     GetThreatProtectionInfo(Profile::FromWebUI(web_ui())));
@@ -660,6 +698,7 @@
     const policy::PolicyMap& /*current*/) {
   UpdateManagedState();
   NotifyBrowserReportingInfoUpdated();
+  NotifyProfileReportingInfoUpdated();
   NotifyThreatProtectionInfoUpdated();
 }
 
diff --git a/chrome/browser/ui/webui/management/management_ui_handler.h b/chrome/browser/ui/webui/management/management_ui_handler.h
index 67a6d3a1..d23c7850 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler.h
+++ b/chrome/browser/ui/webui/management/management_ui_handler.h
@@ -65,7 +65,7 @@
   void OnJavascriptDisallowed() override;
 
  protected:
-  void AddReportingInfo(base::Value::List* report_sources);
+  void AddReportingInfo(base::Value::List* report_sources, bool is_browser);
 
   virtual base::Value::Dict GetContextualManagedData(Profile* profile);
   base::Value::Dict GetThreatProtectionInfo(Profile* profile);
@@ -106,10 +106,12 @@
   void HandleGetManagedWebsites(const base::Value::List& args);
   void HandleGetApplications(const base::Value::List& args);
   void HandleInitBrowserReportingInfo(const base::Value::List& args);
+  void HandleInitProfileReportingInfo(const base::Value::List& args);
 
   void AsyncUpdateLogo();
 
   void NotifyBrowserReportingInfoUpdated();
+  void NotifyProfileReportingInfoUpdated();
 
   // extensions::ExtensionRegistryObserver implementation.
   void OnExtensionLoaded(content::BrowserContext* browser_context,
diff --git a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
index fa73436..8614a94 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
+++ b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
@@ -133,6 +133,7 @@
 using testing::AssertionFailure;
 using testing::AssertionResult;
 using testing::AssertionSuccess;
+using testing::Mock;
 using testing::Return;
 using testing::ReturnRef;
 
@@ -252,7 +253,8 @@
     return GetContextualManagedData(profile);
   }
 
-  base::Value::List GetReportingInfo(bool can_collect_signals = true) {
+  base::Value::List GetReportingInfo(bool can_collect_signals = true,
+                                     bool is_browser = true) {
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
     EXPECT_CALL(mock_user_permission_service_, CanCollectSignals())
         .WillOnce(
@@ -261,7 +263,7 @@
                        : device_signals::UserPermission::kMissingConsent));
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
     base::Value::List report_sources;
-    AddReportingInfo(&report_sources);
+    AddReportingInfo(&report_sources, is_browser);
     return report_sources;
   }
 
@@ -406,6 +408,7 @@
     bool printing_send_username_and_filename;
     bool crostini_report_usage;
     bool cloud_reporting_enabled;
+    bool cloud_profile_reporting_enabled;
     std::string profile_name;
     bool override_policy_connector_is_managed;
     bool managed_account;
@@ -439,6 +442,7 @@
     setup_config_.printing_send_username_and_filename = default_value;
     setup_config_.crostini_report_usage = default_value;
     setup_config_.cloud_reporting_enabled = default_value;
+    setup_config_.cloud_profile_reporting_enabled = default_value;
     setup_config_.profile_name = "";
     setup_config_.override_policy_connector_is_managed = false;
     setup_config_.managed_account = true;
@@ -1594,6 +1598,28 @@
                       expected_messages);
 }
 
+TEST_F(ManagementUIHandlerTests, CloudProfileReportingPolicy) {
+  ResetTestConfig(false);
+  SetUpProfileAndHandler();
+  profile_->GetTestingPrefService()->SetManagedPref(
+      enterprise_reporting::kCloudProfileReportingEnabled,
+      std::make_unique<base::Value>(true));
+
+  std::set<std::string> expected_messages = {
+      kProfileReportingOverview, kProfileReportingUsername,
+      kProfileReportingBrowser, kProfileReportingExtension,
+      kProfileReportingPolicy};
+
+  ASSERT_PRED_FORMAT2(MessagesToBeEQ,
+                      handler_.GetReportingInfo(/*can_collect_signals=*/false,
+                                                /*is_browser=*/true),
+                      {});
+  ASSERT_PRED_FORMAT2(MessagesToBeEQ,
+                      handler_.GetReportingInfo(/*can_collect_signals=*/false,
+                                                /*is_browser=*/false),
+                      expected_messages);
+}
+
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
 TEST_F(ManagementUIHandlerTests,
        CloudReportingPolicyWithoutDeviceSignalsConsent) {
diff --git a/chrome/browser/updates/announcement_notification/announcement_notification_delegate_android.cc b/chrome/browser/updates/announcement_notification/announcement_notification_delegate_android.cc
index 54acdb6..d6ea4a8 100644
--- a/chrome/browser/updates/announcement_notification/announcement_notification_delegate_android.cc
+++ b/chrome/browser/updates/announcement_notification/announcement_notification_delegate_android.cc
@@ -7,8 +7,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "chrome/android/chrome_jni_headers/AnnouncementNotificationManager_jni.h"
-#include "chrome/browser/profiles/profile.h"          // nogncheck
-#include "chrome/browser/profiles/profile_android.h"  // nogncheck
+#include "chrome/browser/profiles/profile.h"  // nogncheck
 #include "chrome/browser/updates/announcement_notification/announcement_notification_service_factory.h"
 
 AnnouncementNotificationDelegateAndroid::
diff --git a/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.cc b/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.cc
index 8fc4984c..c7c5aa1 100644
--- a/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.cc
+++ b/chrome/browser/visited_url_ranking/visited_url_ranking_service_factory.cc
@@ -98,8 +98,7 @@
   if (hs) {
     data_fetchers.emplace(
         Fetcher::kHistory,
-        std::make_unique<visited_url_ranking::HistoryURLVisitDataFetcher>(
-            hs->AsWeakPtr()));
+        std::make_unique<visited_url_ranking::HistoryURLVisitDataFetcher>(hs));
   }
 
   // TODO(crbug.com/329242209): Add various aggregate transformers (e.g,
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
index 3cee5d2..ff0cc39 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
@@ -57,9 +57,8 @@
 
 // ChromeWebAuthenticationDelegate is the //chrome layer implementation of
 // content::WebAuthenticationDelegate.
-class ChromeWebAuthenticationDelegate
-    : public content::WebAuthenticationDelegate,
-      base::SupportsWeakPtr<ChromeWebAuthenticationDelegate> {
+class ChromeWebAuthenticationDelegate final
+    : public content::WebAuthenticationDelegate {
  public:
 #if BUILDFLAG(IS_MAC)
   // Returns a configuration struct for instantiating the macOS WebAuthn
diff --git a/chrome/browser/webauthn/enclave_authenticator_browsertest.cc b/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
index 9c9f850..d88663a 100644
--- a/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
+++ b/chrome/browser/webauthn/enclave_authenticator_browsertest.cc
@@ -794,8 +794,9 @@
     : public EnclaveAuthenticatorBrowserTest {
  public:
   EnclaveAuthenticatorWithPinBrowserTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {device::kWebAuthnEnclaveAuthenticator, device::kWebAuthnGpmPin},
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{device::kWebAuthnEnclaveAuthenticator,
+          {{device::kWebAuthnGpmPin.name, "true"}}}},
         /*disabled_features=*/{
             device::kWebAuthnUseInsecureSoftwareUnexportableKeys});
   }
@@ -1687,10 +1688,10 @@
     : public EnclaveAuthenticatorBrowserTest {
  public:
   EnclaveAuthenticatorWithoutPinBrowserTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {device::kWebAuthnEnclaveAuthenticator},
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{device::kWebAuthnEnclaveAuthenticator,
+          {{device::kWebAuthnGpmPin.name, "false"}}}},
         /*disabled_features=*/{
-            device::kWebAuthnGpmPin,
             device::kWebAuthnUseInsecureSoftwareUnexportableKeys});
   }
 
diff --git a/chrome/browser/webauthn/gpm_enclave_controller.cc b/chrome/browser/webauthn/gpm_enclave_controller.cc
index fc09f9c8..59de311 100644
--- a/chrome/browser/webauthn/gpm_enclave_controller.cc
+++ b/chrome/browser/webauthn/gpm_enclave_controller.cc
@@ -449,7 +449,7 @@
     return;
   }
 
-  if (base::FeatureList::IsEnabled(device::kWebAuthnGpmPin) &&
+  if (device::kWebAuthnGpmPin.Get() &&
       request_type_ == device::FidoRequestType::kGetAssertion) {
     // For get() requests, progress the UI now because, with GPM PIN support,
     // we can handle the account in any state and we'll block the UI if needed
@@ -468,8 +468,7 @@
 
   can_make_uv_keys_ = can_make_uv_keys;
 
-  if (!can_make_uv_keys &&
-      !base::FeatureList::IsEnabled(device::kWebAuthnGpmPin)) {
+  if (!can_make_uv_keys && !device::kWebAuthnGpmPin.Get()) {
     // Without the ability to do user verification, we cannot enroll the current
     // device.
     account_state_ = AccountState::kNone;
@@ -559,7 +558,7 @@
                   << ", has PIN: " << result.gpm_pin_metadata.has_value()
                   << ", iCloud Keychain keys: " << result.icloud_keys.size();
 
-  if (!base::FeatureList::IsEnabled(device::kWebAuthnGpmPin) &&
+  if (!device::kWebAuthnGpmPin.Get() &&
       result.state == Result::State::kRecoverable &&
       !result.lskf_expiries.empty() &&
       base::ranges::all_of(result.lskf_expiries, ExpiryTooSoon)) {
@@ -596,7 +595,7 @@
   }
   security_domain_icloud_recovery_keys_ = std::move(result.icloud_keys);
 
-  if (base::FeatureList::IsEnabled(device::kWebAuthnGpmPin)) {
+  if (device::kWebAuthnGpmPin.Get()) {
     SetActive(account_state_ != AccountState::kNone);
   } else {
     SetActive(account_state_ == AccountState::kRecoverable);
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index d093b18..941bdab6 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1715860514-1baf47c0c8f87f99314c64d6edcc9aba70513bd3-aa8dbada13804c96f1476e8eb6d3ba4441dc6bd2.profdata
+chrome-android32-main-1715882337-e1fe608cb852131bb9ad82f702d2ddbecde71a4a-8aa07a3c65e7242324d8ecc539eb2b7028d754d2.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index d11dedd..5e0f699 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1715831515-709956169f7922d440ccc8289a6c64b5bb460504-a79016b824a0c1e0d8edd2afccb6773cd267d888.profdata
+chrome-android64-main-1715867891-a943a0ee6ee28de9b4d9e6ec64c3256555bccb94-95ed18bf2ff0d000a4a04cc2495a40e5d6623d20.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 1e5b3d3..33c6b37 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1715867891-bdc0058c9cdd06a5e6dfb542ba125f25ce91b1d2-95ed18bf2ff0d000a4a04cc2495a40e5d6623d20.profdata
+chrome-mac-arm-main-1715882337-67df9c6757cb3969409a0b524ea2ad39a5e3475e-8aa07a3c65e7242324d8ecc539eb2b7028d754d2.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index ff0dbd0..5fa7b25 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1715860514-7a724e7d938a4ce9c812b7431aecc3031ffc85c0-aa8dbada13804c96f1476e8eb6d3ba4441dc6bd2.profdata
+chrome-win-arm64-main-1715882337-98a5fc7f293838023e412b552823e54cf10740c6-8aa07a3c65e7242324d8ecc539eb2b7028d754d2.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index cc82715..bc062d59 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1715860514-1f17de3cee22ec4aa76fc405b3e1ce44ca506318-aa8dbada13804c96f1476e8eb6d3ba4441dc6bd2.profdata
+chrome-win32-main-1715871577-5bea9a3232aef617fa7fbfe1c27f68f34e9f4437-17025b9a22c8a2d47e89990a872307cc1c3923e8.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 74c9005..129a76b 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1715860514-f8f1b2ca42452b16900647e3da3aec8f52f5c391-aa8dbada13804c96f1476e8eb6d3ba4441dc6bd2.profdata
+chrome-win64-main-1715871577-bbe429c630625c6f97b4a233577fc5faa0822b33-17025b9a22c8a2d47e89990a872307cc1c3923e8.profdata
diff --git a/chrome/installer/mac/signing/driver.py b/chrome/installer/mac/signing/driver.py
index 2ab9e2d..2fd03817 100644
--- a/chrome/installer/mac/signing/driver.py
+++ b/chrome/installer/mac/signing/driver.py
@@ -55,6 +55,13 @@
             def inject_get_task_allow_entitlement(self):
                 return True
 
+            @property
+            def main_executable_pinned_geometry(self):
+                # Pinned geometry is only needed to keep release builds
+                # consistent over time. Ignore executable geometry when code
+                # signing for development.
+                return None
+
         config_class = DevelopmentCodeSignConfig
 
     return config_class(**config_args)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 877d5806..85e1719 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -8820,6 +8820,7 @@
       "//chrome/browser/screen_ai/public:test_support",
       "//chrome/browser/ui/ash/holding_space:test_support",
       "//chrome/browser/ui/chromeos/magic_boost",
+      "//chrome/browser/ui/chromeos/magic_boost:test_support",
       "//chrome/browser/ui/chromeos/read_write_cards",
       "//chrome/browser/ui/quick_answers",
       "//chrome/browser/ui/views/editor_menu:utils",
diff --git a/chrome/test/data/extensions/signin_screen_manual_test_extension/README b/chrome/test/data/extensions/signin_screen_manual_test_extension/README
index d4d72825..9325504 100644
--- a/chrome/test/data/extensions/signin_screen_manual_test_extension/README
+++ b/chrome/test/data/extensions/signin_screen_manual_test_extension/README
@@ -1,5 +1,5 @@
 The extension_signed_by_webstore.crx package must be a one signed by WebStore,
-in order for the extension to have the expected ID which is whitelisted in
+in order for the extension to have the expected ID which is allowlisted in
 Chrome - "ngjobkbdodapjbbncmagbccommkggmnj".
 
 This extension is primarily intended to be used for the manual testing of the
diff --git a/chrome/test/data/extensions/signin_screen_manual_test_extension/extension/manifest.json b/chrome/test/data/extensions/signin_screen_manual_test_extension/extension/manifest.json
index a682fd6..04d9908 100644
--- a/chrome/test/data/extensions/signin_screen_manual_test_extension/extension/manifest.json
+++ b/chrome/test/data/extensions/signin_screen_manual_test_extension/extension/manifest.json
@@ -1,9 +1,9 @@
 {
   "name": "Sign-in Screen Test Extension",
-  "version": "2.0",
-  "manifest_version": 2,
+  "version": "3.0",
+  "manifest_version": 3,
   "description": "The extension for manual testing of the extensions installation in the Chrome OS sign-in profile",
   "background": {
-    "scripts": ["background.js"]
+    "service_worker": "background.js"
   }
 }
diff --git a/chrome/test/data/extensions/signin_screen_manual_test_extension/extension_signed_by_webstore.crx b/chrome/test/data/extensions/signin_screen_manual_test_extension/extension_signed_by_webstore.crx
index c9fadd51..6c46a223 100644
--- a/chrome/test/data/extensions/signin_screen_manual_test_extension/extension_signed_by_webstore.crx
+++ b/chrome/test/data/extensions/signin_screen_manual_test_extension/extension_signed_by_webstore.crx
Binary files differ
diff --git a/chrome/test/data/extensions/signin_screen_manual_test_extension/update_manifest.xml b/chrome/test/data/extensions/signin_screen_manual_test_extension/update_manifest.xml
index e571080..27d6b2a 100644
--- a/chrome/test/data/extensions/signin_screen_manual_test_extension/update_manifest.xml
+++ b/chrome/test/data/extensions/signin_screen_manual_test_extension/update_manifest.xml
@@ -7,6 +7,6 @@
   <app appid='ngjobkbdodapjbbncmagbccommkggmnj'>
     <updatecheck
         codebase='http://mock.http/extensions/signin_screen_manual_test_extension/extension_signed_by_webstore.crx'
-        version='2.0' />
+        version='3.0' />
    </app>
 </gupdate>
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn b/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn
index 0f13996..68641691 100644
--- a/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn
@@ -15,6 +15,7 @@
   ]
 
   files = [
+    "capabilities_manager_test.ts",
     "destination_dropdown_controller_test.ts",
     "destination_dropdown_test.ts",
     "destination_manager_test.ts",
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/capabilities_manager_test.ts b/chrome/test/data/webui/chromeos/print_preview_cros/capabilities_manager_test.ts
new file mode 100644
index 0000000..b7ec6b6d
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/capabilities_manager_test.ts
@@ -0,0 +1,147 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://os-print/js/data/capabilities_manager.js';
+
+import {CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_LOADING, CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_READY, CAPABILITIES_MANAGER_SESSION_INITIALIZED, CapabilitiesManager} from 'chrome://os-print/js/data/capabilities_manager.js';
+import {DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED, DestinationManager} from 'chrome://os-print/js/data/destination_manager.js';
+import {getFakeCapabilities} from 'chrome://os-print/js/fakes/fake_data.js';
+import {FakeDestinationProvider} from 'chrome://os-print/js/fakes/fake_destination_provider.js';
+import {FAKE_PRINT_SESSION_CONTEXT_SUCCESSFUL} from 'chrome://os-print/js/fakes/fake_print_preview_page_handler.js';
+import {createCustomEvent} from 'chrome://os-print/js/utils/event_utils.js';
+import {setDestinationProviderForTesting} from 'chrome://os-print/js/utils/mojo_data_providers.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
+import {MockController} from 'chrome://webui-test/chromeos/mock_controller.m.js';
+import {eventToPromise} from 'chrome://webui-test/test_util.js';
+
+import {createTestDestination} from './test_utils.js';
+
+suite('CapabilitiesManager', () => {
+  let destinationProvider: FakeDestinationProvider;
+  let mockController: MockController;
+
+  setup(() => {
+    CapabilitiesManager.resetInstanceForTesting();
+    DestinationManager.resetInstanceForTesting();
+
+    // Setup fakes for testing.
+    mockController = new MockController();
+    destinationProvider = new FakeDestinationProvider();
+    setDestinationProviderForTesting(destinationProvider);
+  });
+
+  teardown(() => {
+    mockController.reset();
+    CapabilitiesManager.resetInstanceForTesting();
+    DestinationManager.resetInstanceForTesting();
+  });
+
+  // Initialize the DestinationManager and wait for it to send all events.
+  async function waitForDestinationManagerLoad(): Promise<void> {
+    const destinationManager = DestinationManager.getInstance();
+    const activeDestinationChangedEvent = eventToPromise(
+        DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED, destinationManager);
+    return activeDestinationChangedEvent;
+  }
+
+
+  test('is a singleton', () => {
+    const instance1 = CapabilitiesManager.getInstance();
+    const instance2 = CapabilitiesManager.getInstance();
+    assertEquals(instance1, instance2);
+  });
+
+  test('can clear singleton', () => {
+    const instance1 = CapabilitiesManager.getInstance();
+    CapabilitiesManager.resetInstanceForTesting();
+    const instance2 = CapabilitiesManager.getInstance();
+    assertTrue(instance1 !== instance2);
+  });
+
+  // Verify `isSessionInitialized` returns true and triggers
+  // CAPABILITIES_MANAGER_SESSION_INITIALIZED event after `initializeSession`
+  // called.
+  test(
+      'initializeSession updates isSessionInitialized and triggers ' +
+          CAPABILITIES_MANAGER_SESSION_INITIALIZED,
+      async () => {
+        await waitForDestinationManagerLoad();
+
+        const instance = CapabilitiesManager.getInstance();
+        assertFalse(
+            instance.isSessionInitialized(),
+            'Before initializeSession, instance should not be initialized');
+
+        // Set initial context.
+        const sessionInit =
+            eventToPromise(CAPABILITIES_MANAGER_SESSION_INITIALIZED, instance);
+        instance.initializeSession(FAKE_PRINT_SESSION_CONTEXT_SUCCESSFUL);
+        await sessionInit;
+
+        assertTrue(
+            instance.isSessionInitialized(),
+            'After initializeSession, instance should be initialized');
+      });
+
+  // Verify when Destination Manager signals the active destination changed, the
+  // Capabilities Manager fetches capabilities.
+  test(
+      'fetch capabilities on active destination changed and cache response',
+      async () => {
+        // Set the active destination.
+        const activeCapailities = getFakeCapabilities();
+        const activeDestination =
+            createTestDestination(activeCapailities.destinationId);
+        destinationProvider.setCapabiltiies(activeCapailities);
+
+        await waitForDestinationManagerLoad();
+
+        const destinationManager = DestinationManager.getInstance();
+        const getActiveDestinationFn = mockController.createFunctionMock(
+            destinationManager, 'getActiveDestination');
+        getActiveDestinationFn.returnValue = activeDestination;
+
+        const instance = CapabilitiesManager.getInstance();
+        instance.initializeSession(FAKE_PRINT_SESSION_CONTEXT_SUCCESSFUL);
+        let providerCallCount = 0;
+        assertEquals(
+            providerCallCount,
+            destinationProvider.getCallCount('fetchCapabilities'));
+
+        // Simulate the active destination change and wait for the capabilities
+        // manager to signal capabilities are ready.
+        let capsLoadingEvent = eventToPromise(
+            CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_LOADING, instance);
+        let capsReadyEvent = eventToPromise(
+            CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_READY, instance);
+        destinationManager.dispatchEvent(
+            createCustomEvent(DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED));
+        ++providerCallCount;
+        await capsLoadingEvent;
+        await capsReadyEvent;
+
+        assertEquals(
+            providerCallCount,
+            destinationProvider.getCallCount('fetchCapabilities'));
+        assertDeepEquals(
+            activeCapailities, instance.getActiveDestinationCapabilities());
+
+        // Simulate the active destination changing again except this time the
+        // cached capabilities result is returned.
+        capsLoadingEvent = eventToPromise(
+            CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_LOADING, instance);
+        capsReadyEvent = eventToPromise(
+            CAPABILITIES_MANAGER_ACTIVE_DESTINATION_CAPS_READY, instance);
+        destinationManager.dispatchEvent(
+            createCustomEvent(DESTINATION_MANAGER_ACTIVE_DESTINATION_CHANGED));
+        await capsLoadingEvent;
+        await capsReadyEvent;
+
+        assertEquals(
+            providerCallCount,
+            destinationProvider.getCallCount('fetchCapabilities'));
+        assertDeepEquals(
+            activeCapailities, instance.getActiveDestinationCapabilities());
+      });
+});
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_app_controller_test.ts b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_app_controller_test.ts
index da63f9c..c16d7d6 100644
--- a/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_app_controller_test.ts
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_app_controller_test.ts
@@ -4,6 +4,7 @@
 
 import 'chrome://os-print/js/print_preview_cros_app_controller.js';
 
+import {CAPABILITIES_MANAGER_SESSION_INITIALIZED, CapabilitiesManager} from 'chrome://os-print/js/data/capabilities_manager.js';
 import {DESTINATION_MANAGER_SESSION_INITIALIZED, DestinationManager} from 'chrome://os-print/js/data/destination_manager.js';
 import {PREVIEW_TICKET_MANAGER_SESSION_INITIALIZED, PreviewTicketManager} from 'chrome://os-print/js/data/preview_ticket_manager.js';
 import {PRINT_TICKET_MANAGER_SESSION_INITIALIZED, PrintTicketManager} from 'chrome://os-print/js/data/print_ticket_manager.js';
@@ -25,6 +26,7 @@
     mockTimer = new MockTimer();
     mockTimer.install();
 
+    CapabilitiesManager.resetInstanceForTesting();
     DestinationManager.resetInstanceForTesting();
     PrintTicketManager.resetInstanceForTesting();
     PreviewTicketManager.resetInstanceForTesting();
@@ -36,6 +38,7 @@
 
   teardown(() => {
     printPreviewPageHandler.reset();
+    CapabilitiesManager.resetInstanceForTesting();
     DestinationManager.resetInstanceForTesting();
     PreviewTicketManager.resetInstanceForTesting();
     PrintTicketManager.resetInstanceForTesting();
@@ -145,4 +148,29 @@
             'After initializeSession PreviewTicketManager instance should be ' +
                 'initialized');
       });
+
+  // Verify capabilities manager is initialized after start session resolves.
+  test(
+      'on resolve of startSession calls CapabilitiesManager.initializeSession',
+      async () => {
+        printPreviewPageHandler.setTestDelay(testDelay);
+
+        const controller = new PrintPreviewCrosAppController();
+        assertTrue(!!controller, 'Unable to create controller');
+        const capabilitiesManager = CapabilitiesManager.getInstance();
+        assertFalse(
+            capabilitiesManager.isSessionInitialized(),
+            'Before initializeSession CapabilitiesManager instance should ' +
+                'not be initialized');
+
+        // Move timer forward to resolve startSession.
+        mockTimer.tick(testDelay);
+        await eventToPromise(
+            CAPABILITIES_MANAGER_SESSION_INITIALIZED, capabilitiesManager);
+
+        assertTrue(
+            capabilitiesManager.isSessionInitialized(),
+            'After initializeSession CapabilitiesManager instance should be ' +
+                'initialized');
+      });
 });
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc
index 350a9cc..f459a53 100644
--- a/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc
@@ -36,6 +36,10 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+IN_PROC_BROWSER_TEST_F(PrintPreviewCrosBrowserTest, CapabilitiesManagerTest) {
+  RunTestAtPath("capabilities_manager_test.js");
+}
+
 IN_PROC_BROWSER_TEST_F(PrintPreviewCrosBrowserTest,
                        DestinationDropdownControllerTest) {
   RunTestAtPath("destination_dropdown_controller_test.js");
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/print_ticket_manager_test.ts b/chrome/test/data/webui/chromeos/print_preview_cros/print_ticket_manager_test.ts
index ce51feb..022b38c 100644
--- a/chrome/test/data/webui/chromeos/print_preview_cros/print_ticket_manager_test.ts
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/print_ticket_manager_test.ts
@@ -149,8 +149,8 @@
         assertFalse(instance.isPrintRequestInProgress(), 'Request finished');
       });
 
-  // Verify PrintTicketManger ensures that PrintPreviewPageHandler.print is only
-  // called if print request is not in progress.
+  // Verify PrintTicketManager ensures that PrintPreviewPageHandler.print is
+  // only called if print request is not in progress.
   test('ensure only one print request triggered at a time', async () => {
     const delay = 1;
     printPreviewPageHandler.setTestDelay(delay);
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/summary_panel_controller_test.ts b/chrome/test/data/webui/chromeos/print_preview_cros/summary_panel_controller_test.ts
index b4b36a97..db32e9a5 100644
--- a/chrome/test/data/webui/chromeos/print_preview_cros/summary_panel_controller_test.ts
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/summary_panel_controller_test.ts
@@ -19,8 +19,8 @@
   let controller: SummaryPanelController|null = null;
   let mockController: MockController;
   let printPreviewPageHandler: FakePrintPreviewPageHandler;
-  let previewTicketManger: PreviewTicketManager;
-  let printTicketManger: PrintTicketManager;
+  let previewTicketManager: PreviewTicketManager;
+  let printTicketManager: PrintTicketManager;
   let eventTracker: EventTracker;
 
   setup(() => {
@@ -31,8 +31,8 @@
     // Setup fakes.
     printPreviewPageHandler = new FakePrintPreviewPageHandler();
     setPrintPreviewPageHandlerForTesting(printPreviewPageHandler);
-    previewTicketManger = PreviewTicketManager.getInstance();
-    printTicketManger = PrintTicketManager.getInstance();
+    previewTicketManager = PreviewTicketManager.getInstance();
+    printTicketManager = PrintTicketManager.getInstance();
 
     controller = new SummaryPanelController(eventTracker);
     assertTrue(!!controller);
@@ -95,10 +95,10 @@
     const testEvent = new CustomEvent<void>(
         PRINT_REQUEST_STARTED_EVENT, {bubbles: true, composed: true});
     const startRequest =
-        eventToPromise(PRINT_REQUEST_STARTED_EVENT, printTicketManger);
+        eventToPromise(PRINT_REQUEST_STARTED_EVENT, printTicketManager);
     handlerFn.addExpectation(testEvent);
 
-    printTicketManger.dispatchEvent(testEvent);
+    printTicketManager.dispatchEvent(testEvent);
     await startRequest;
 
     mockController.verifyMocks();
@@ -113,10 +113,10 @@
         const testEvent = new CustomEvent<void>(
             PRINT_REQUEST_FINISHED_EVENT, {bubbles: true, composed: true});
         const finishRequest =
-            eventToPromise(PRINT_REQUEST_FINISHED_EVENT, printTicketManger);
+            eventToPromise(PRINT_REQUEST_FINISHED_EVENT, printTicketManager);
         handlerFn.addExpectation(testEvent);
 
-        printTicketManger.dispatchEvent(testEvent);
+        printTicketManager.dispatchEvent(testEvent);
         await finishRequest;
 
         mockController.verifyMocks();
@@ -130,11 +130,11 @@
         // Set preview loaded to true since that can also disable the print
         // button.
         const previewRequestInProgressFn = mockController.createFunctionMock(
-            previewTicketManger, 'isPreviewLoaded');
+            previewTicketManager, 'isPreviewLoaded');
         previewRequestInProgressFn.returnValue = true;
 
         const printRequestInProgressFn = mockController.createFunctionMock(
-            printTicketManger, 'isPrintRequestInProgress');
+            printTicketManager, 'isPrintRequestInProgress');
         printRequestInProgressFn.returnValue = true;
         assertTrue(controller!.shouldDisablePrintButton());
       });
@@ -147,11 +147,11 @@
         // Set preview loaded to true since that can also disable the print
         // button.
         const previewRequestInProgressFn = mockController.createFunctionMock(
-            previewTicketManger, 'isPreviewLoaded');
+            previewTicketManager, 'isPreviewLoaded');
         previewRequestInProgressFn.returnValue = true;
 
         const printRequestInProgressFn = mockController.createFunctionMock(
-            printTicketManger, 'isPrintRequestInProgress');
+            printTicketManager, 'isPrintRequestInProgress');
         printRequestInProgressFn.returnValue = false;
         assertFalse(controller!.shouldDisablePrintButton());
       });
@@ -159,7 +159,7 @@
   // Verify shouldDisablePrintButton is true when preview isn't loaded.
   test('shouldDisablePrintButton true while preview is not loaded', () => {
     const previewRequestInProgressFn = mockController.createFunctionMock(
-        previewTicketManger, 'isPreviewLoaded');
+        previewTicketManager, 'isPreviewLoaded');
     previewRequestInProgressFn.returnValue = false;
     assertTrue(controller!.shouldDisablePrintButton());
   });
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/summary_panel_test.ts b/chrome/test/data/webui/chromeos/print_preview_cros/summary_panel_test.ts
index 751b53a..e08123f 100644
--- a/chrome/test/data/webui/chromeos/print_preview_cros/summary_panel_test.ts
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/summary_panel_test.ts
@@ -80,10 +80,10 @@
   // Initialize the session to request a preview and enable the print button.
   async function waitForPreviewRequestFinished(delay: number = 1):
       Promise<void> {
-    const previewTicketManger = PreviewTicketManager.getInstance();
+    const previewTicketManager = PreviewTicketManager.getInstance();
     const previewRequestFinishedEvent =
-        eventToPromise(PREVIEW_REQUEST_FINISHED_EVENT, previewTicketManger);
-    previewTicketManger.initializeSession(
+        eventToPromise(PREVIEW_REQUEST_FINISHED_EVENT, previewTicketManager);
+    previewTicketManager.initializeSession(
         FAKE_PRINT_SESSION_CONTEXT_SUCCESSFUL);
     mockTimer.tick(delay);
     await previewRequestFinishedEvent;
@@ -201,13 +201,13 @@
     assertFalse(
         printButton.disabled, 'Print should be enabled before request sent');
 
-    const printTicketManger = PrintTicketManager.getInstance();
-    printTicketManger.initializeSession(FAKE_PRINT_SESSION_CONTEXT_SUCCESSFUL);
+    const printTicketManager = PrintTicketManager.getInstance();
+    printTicketManager.initializeSession(FAKE_PRINT_SESSION_CONTEXT_SUCCESSFUL);
     printButton.click();
     await printDisabledEvent1;
 
     assertTrue(
-        printTicketManger.isPrintRequestInProgress(),
+        printTicketManager.isPrintRequestInProgress(),
         'Print request in progress');
     assertTrue(
         printButton.disabled,
@@ -219,7 +219,7 @@
     await printDisabledEvent2;
 
     assertFalse(
-        printTicketManger.isPrintRequestInProgress(),
+        printTicketManager.isPrintRequestInProgress(),
         'Print request is complete');
     assertFalse(
         printButton.disabled,
diff --git a/chrome/test/data/webui/compose/compose_app_focus_test.ts b/chrome/test/data/webui/compose/compose_app_focus_test.ts
index 76d4639d..d2590d7 100644
--- a/chrome/test/data/webui/compose/compose_app_focus_test.ts
+++ b/chrome/test/data/webui/compose/compose_app_focus_test.ts
@@ -4,6 +4,7 @@
 
 import 'chrome-untrusted://compose/app.js';
 
+import {loadTimeData} from '//resources/js/load_time_data.js';
 import type {ComposeAppElement} from 'chrome-untrusted://compose/app.js';
 import { StyleModifier, UserFeedback } from 'chrome-untrusted://compose/compose.mojom-webui.js';
 import {ComposeApiProxyImpl} from 'chrome-untrusted://compose/compose_api_proxy.js';
@@ -66,6 +67,11 @@
   });
 
   test('FocusesRefreshButtonAfterRefreshRewrite', async () => {
+    // This test is only useful for non-refinement UI.
+    loadTimeData.overrideValues({
+      enableRefinedUi: false,
+    });
+
     const app = await createApp();
     app.$.textarea.value = 'test value one';
     app.$.submitButton.click();
@@ -90,6 +96,11 @@
   });
 
   test('FocusesLengthMenuAfterLengthRewrite', async () => {
+    // This test is only useful for non-refinement UI.
+    loadTimeData.overrideValues({
+      enableRefinedUi: false,
+    });
+
     const app = await createApp();
     app.$.textarea.value = 'test value';
     app.$.submitButton.click();
diff --git a/chrome/test/data/webui/compose/compose_app_test.ts b/chrome/test/data/webui/compose/compose_app_test.ts
index 98e0705..a49e230 100644
--- a/chrome/test/data/webui/compose/compose_app_test.ts
+++ b/chrome/test/data/webui/compose/compose_app_test.ts
@@ -148,64 +148,6 @@
         appWithNoTextSelected.$.acceptButton.textContent!, 'Insert');
   });
 
-  test('RefreshesResult', async () => {
-    // Submit the input once so the refresh button shows up.
-    mockInput('Input to refresh.');
-    app.$.submitButton.click();
-    await mockResponse();
-
-    testProxy.resetResolver('rewrite');
-    assertTrue(
-        isVisible(app.$.refreshButton), 'Refresh button should be visible.');
-
-    // Click the refresh button and assert compose is called with the same args.
-    app.$.refreshButton.click();
-    assertTrue(
-        isVisible(app.$.loading), 'Loading indicator should be visible.');
-
-    const args = await testProxy.whenCalled('rewrite');
-    await mockResponse('Refreshed output.');
-
-    assertEquals(StyleModifier.kRetry, args);
-
-    // Verify UI has updated with refreshed results.
-    assertFalse(isVisible(app.$.loading));
-    assertTrue(
-        isVisible(app.$.resultContainer),
-        'App result container should be visible.');
-    assertStringContains(
-        app.$.resultText.$.root.innerText, 'Refreshed output.');
-  });
-
-  test('UpdatesScrollableBodyAfterResize', async () => {
-    assertTrue(app.$.body.hasAttribute('scrollable'));
-
-    mockInput('Some fake input.');
-    app.$.submitButton.click();
-
-    // Mock a height on results to get body to scroll. The body should not yet
-    // be scrollable though because result has not been fetched yet.
-    app.$.resultContainer.style.minHeight = '500px';
-    assertFalse(app.$.body.classList.contains('can-scroll'));
-
-    await testProxy.whenCalled('compose');
-    await mockResponse();
-    await whenCheck(
-        app.$.body, () => app.$.body.classList.contains('can-scroll'));
-    assertEquals(220, app.$.body.offsetHeight);
-    assertTrue(220 < app.$.body.scrollHeight);
-
-    // Mock resizing result container down to a 50px height. This should result
-    // in the body changing height, triggering the updates to the CSS classes.
-    // At this point, 50px is too short to scroll, so it should not have the
-    // 'can-scroll' class.
-    app.$.resultContainer.style.minHeight = '50px';
-    app.$.resultContainer.style.height = '50px';
-    app.$.resultContainer.style.overflow = 'hidden';
-    await whenCheck(
-        app.$.body, () => !app.$.body.classList.contains('can-scroll'));
-  });
-
   test('FirstRunAndMsbbStateDetermineViewState', async () => {
     // Check correct visibility for FRE view state.
     const appWithFirstRunDialog =
@@ -628,41 +570,6 @@
     assertStringContains(app.$.resultText.$.root.innerText, 'new response');
   });
 
-  test('ComposeWithLengthToneOptionResult', async () => {
-    // Submit the input once so the refresh button shows up.
-    mockInput('Input to refresh.');
-    app.$.submitButton.click();
-    await mockResponse();
-
-    testProxy.resetResolver('rewrite');
-
-    assertTrue(isVisible(app.$.lengthMenu), 'Length menu should be visible.');
-    assertEquals(
-        2, app.$.lengthMenu.querySelectorAll('option:not([disabled])').length);
-
-    app.$.lengthMenu.value = `${StyleModifier.kShorter}`;
-    app.$.lengthMenu.dispatchEvent(new CustomEvent('change'));
-
-    const args = await testProxy.whenCalled('rewrite');
-    await mockResponse();
-
-    assertEquals(StyleModifier.kShorter, args);
-
-    testProxy.resetResolver('rewrite');
-
-    assertTrue(isVisible(app.$.toneMenu), 'Tone menu should be visible.');
-    assertEquals(
-        2, app.$.toneMenu.querySelectorAll('option:not([disabled])').length);
-
-    app.$.toneMenu.value = `${StyleModifier.kCasual}`;
-    app.$.toneMenu.dispatchEvent(new CustomEvent('change'));
-
-    const args2 = await testProxy.whenCalled('rewrite');
-    await mockResponse();
-
-    assertEquals(StyleModifier.kCasual, args2);
-  });
-
   test('Undo', async () => {
     // Set up initial state to show undo button and mock up a previous state.
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
@@ -828,3 +735,247 @@
     assertEquals(app.$.resultText.$.root.innerText.trim(), 'some response');
   });
 });
+
+suite('ComposeAppLegacyUi', () => {
+  let app: ComposeAppElement;
+  let testProxy: TestComposeApiProxy;
+
+  suiteSetup(function() {
+    if (loadTimeData.getBoolean('enableRefinedUi')) {
+      this.skip();
+    }
+  });
+
+  setup(async () => {
+    testProxy = new TestComposeApiProxy();
+    ComposeApiProxyImpl.setInstance(testProxy);
+
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    app = document.createElement('compose-app');
+    document.body.appendChild(app);
+
+    await testProxy.whenCalled('requestInitialState');
+    return flushTasks();
+  });
+
+  function mockInput(input: string) {
+    app.$.textarea.value = input;
+    app.$.textarea.dispatchEvent(new CustomEvent('value-changed'));
+  }
+
+  function mockResponse(
+      result: string = 'some response',
+      status: ComposeStatus = ComposeStatus.kOk, onDeviceEvaluationUsed = false,
+      triggeredFromModifier = false): Promise<void> {
+    testProxy.remote.responseReceived({
+      status: status,
+      undoAvailable: false,
+      redoAvailable: false,
+      providedByUser: false,
+      result,
+      onDeviceEvaluationUsed,
+      triggeredFromModifier,
+    });
+    return testProxy.remote.$.flushForTesting();
+  }
+
+  test('RefreshesResult', async () => {
+    // Submit the input once so the refresh button shows up.
+    mockInput('Input to refresh.');
+    app.$.submitButton.click();
+    await mockResponse();
+
+    testProxy.resetResolver('rewrite');
+    assertTrue(
+        isVisible(app.$.refreshButton), 'Refresh button should be visible.');
+
+    // Click the refresh button and assert compose is called with the same args.
+    app.$.refreshButton.click();
+    assertTrue(
+        isVisible(app.$.loading), 'Loading indicator should be visible.');
+
+    const args = await testProxy.whenCalled('rewrite');
+    await mockResponse('Refreshed output.');
+
+    assertEquals(StyleModifier.kRetry, args);
+
+    // Verify UI has updated with refreshed results.
+    assertFalse(isVisible(app.$.loading));
+    assertTrue(
+        isVisible(app.$.resultContainer),
+        'App result container should be visible.');
+    assertStringContains(
+        app.$.resultText.$.root.innerText, 'Refreshed output.');
+  });
+
+  test('UpdatesScrollableBodyAfterResize', async () => {
+    assertTrue(app.$.body.hasAttribute('scrollable'));
+
+    mockInput('Some fake input.');
+    app.$.submitButton.click();
+
+    // Mock a height on results to get body to scroll. The body should not yet
+    // be scrollable though because result has not been fetched yet.
+    app.$.resultContainer.style.minHeight = '500px';
+    assertFalse(app.$.body.classList.contains('can-scroll'));
+
+    await testProxy.whenCalled('compose');
+    await mockResponse();
+    await whenCheck(
+        app.$.body, () => app.$.body.classList.contains('can-scroll'));
+    assertEquals(220, app.$.body.offsetHeight);
+    assertTrue(220 < app.$.body.scrollHeight);
+
+    // Mock resizing result container down to a 50px height. This should result
+    // in the body changing height, triggering the updates to the CSS classes.
+    // At this point, 50px is too short to scroll, so it should not have the
+    // 'can-scroll' class.
+    app.$.resultContainer.style.minHeight = '50px';
+    app.$.resultContainer.style.height = '50px';
+    app.$.resultContainer.style.overflow = 'hidden';
+    await whenCheck(
+        app.$.body, () => !app.$.body.classList.contains('can-scroll'));
+  });
+
+  test('ComposeWithLengthToneOptionResult', async () => {
+    // Submit the input once so the refresh button shows up.
+    mockInput('Input to refresh.');
+    app.$.submitButton.click();
+    await mockResponse();
+
+    testProxy.resetResolver('rewrite');
+
+    assertTrue(isVisible(app.$.lengthMenu), 'Length menu should be visible.');
+    assertEquals(
+        2, app.$.lengthMenu.querySelectorAll('option:not([disabled])').length);
+
+    app.$.lengthMenu.value = `${StyleModifier.kShorter}`;
+    app.$.lengthMenu.dispatchEvent(new CustomEvent('change'));
+
+    const args = await testProxy.whenCalled('rewrite');
+    await mockResponse();
+
+    assertEquals(StyleModifier.kShorter, args);
+
+    testProxy.resetResolver('rewrite');
+
+    assertTrue(isVisible(app.$.toneMenu), 'Tone menu should be visible.');
+    assertEquals(
+        2, app.$.toneMenu.querySelectorAll('option:not([disabled])').length);
+
+    app.$.toneMenu.value = `${StyleModifier.kCasual}`;
+    app.$.toneMenu.dispatchEvent(new CustomEvent('change'));
+
+    const args2 = await testProxy.whenCalled('rewrite');
+    await mockResponse();
+
+    assertEquals(StyleModifier.kCasual, args2);
+  });
+});
+
+
+suite('ComposeAppRefinedUi', () => {
+  let app: ComposeAppElement;
+  let testProxy: TestComposeApiProxy;
+
+  suiteSetup(function() {
+    if (!loadTimeData.getBoolean('enableRefinedUi')) {
+      this.skip();
+    }
+  });
+
+  setup(async () => {
+    testProxy = new TestComposeApiProxy();
+    ComposeApiProxyImpl.setInstance(testProxy);
+
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    app = document.createElement('compose-app');
+    document.body.appendChild(app);
+
+    await testProxy.whenCalled('requestInitialState');
+    return flushTasks();
+  });
+
+  function mockInput(input: string) {
+    app.$.textarea.value = input;
+    app.$.textarea.dispatchEvent(new CustomEvent('value-changed'));
+  }
+
+  function mockResponse(
+      result: string = 'some response',
+      status: ComposeStatus = ComposeStatus.kOk, onDeviceEvaluationUsed = false,
+      triggeredFromModifier = false): Promise<void> {
+    testProxy.remote.responseReceived({
+      status: status,
+      undoAvailable: false,
+      redoAvailable: false,
+      providedByUser: false,
+      result,
+      onDeviceEvaluationUsed,
+      triggeredFromModifier,
+    });
+    return testProxy.remote.$.flushForTesting();
+  }
+
+  test('RefreshesResult', async () => {
+    // Submit the input once so that modifier menu is visible.
+    mockInput('Input to retry.');
+    app.$.submitButton.click();
+    await mockResponse();
+
+    testProxy.resetResolver('rewrite');
+    assertTrue(
+        isVisible(app.$.modifierMenu), 'Modifier menu should be visible.');
+
+    // Select the retry option from the modifier menu and assert compose is
+    // called with the same args.
+    app.$.modifierMenu.value = `${StyleModifier.kRetry}`;
+    app.$.modifierMenu.dispatchEvent(new CustomEvent('change'));
+    assertTrue(
+        isVisible(app.$.loading), 'Loading indicator should be visible.');
+
+    const args = await testProxy.whenCalled('rewrite');
+    await mockResponse('Refreshed output.');
+
+    assertEquals(StyleModifier.kRetry, args);
+
+    // Verify UI has updated with refreshed results.
+    assertFalse(isVisible(app.$.loading));
+    assertTrue(
+        isVisible(app.$.resultContainer),
+        'App result container should be visible.');
+    assertStringContains(
+        app.$.resultText.$.root.innerText, 'Refreshed output.');
+  });
+
+  test('UpdatesScrollableResultContainerAfterResize', async () => {
+    assertTrue(app.$.resultTextContainer.hasAttribute('scrollable'));
+    mockInput('Some fake input.');
+    app.$.submitButton.click();
+
+    // The results text should not yet be scrollable because the result has not
+    // been fetched yet.
+    assertFalse(app.$.resultTextContainer.classList.contains('can-scroll'));
+
+    // Results text should be scrollable when a long response is received.
+    await testProxy.whenCalled('compose');
+    const longResponse = 'x'.repeat(1000);
+    await mockResponse(longResponse);
+    await whenCheck(
+        app.$.resultTextContainer,
+        () => app.$.resultTextContainer.classList.contains('can-scroll'));
+    assertEquals(220, app.$.body.offsetHeight);
+    assertTrue(
+        220 < app.$.resultTextContainer.scrollHeight,
+        'Scroll height (' + app.$.resultTextContainer.scrollHeight +
+            ' should be bigger than 220.');
+
+    // Results text should not be scrollable when a short response is received.
+    app.$.modifierMenu.value = `${StyleModifier.kRetry}`;
+    app.$.modifierMenu.dispatchEvent(new CustomEvent('change'));
+    await testProxy.whenCalled('rewrite');
+    await mockResponse('Refreshed output.');
+    await whenCheck(
+        app.$.body, () => !app.$.body.classList.contains('can-scroll'));
+  });
+});
diff --git a/chrome/test/data/webui/compose/compose_browsertest.cc b/chrome/test/data/webui/compose/compose_browsertest.cc
index 3437f1e..96caf31 100644
--- a/chrome/test/data/webui/compose/compose_browsertest.cc
+++ b/chrome/test/data/webui/compose/compose_browsertest.cc
@@ -8,36 +8,50 @@
 #include "components/compose/core/browser/compose_features.h"
 #include "content/public/test/browser_test.h"
 
-class ComposeTest : public WebUIMochaBrowserTest {
+class ComposeTest : public WebUIMochaBrowserTest,
+                    public testing::WithParamInterface<bool> {
+ public:
+  static std::string DescribeParams(
+      const testing::TestParamInfo<ParamType>& info) {
+    return info.param ? "RefinedUI" : "LegacyUI";
+  }
+
  protected:
   ComposeTest() {
     set_test_loader_host(chrome::kChromeUIUntrustedComposeHost);
     set_test_loader_scheme(content::kChromeUIUntrustedScheme);
     scoped_compose_enabled_ = ComposeEnabling::ScopedEnableComposeForTesting();
+    scoped_feature_list_.InitWithFeatureStates(
+        {{compose::features::kEnableCompose, true},
+         {compose::features::kComposeUiRefinement, GetParam()}});
   }
 
  private:
-  base::test::ScopedFeatureList scoped_feature_list_{
-      compose::features::kEnableCompose};
+  base::test::ScopedFeatureList scoped_feature_list_;
   ComposeEnabling::ScopedOverride scoped_compose_enabled_;
 };
 
-IN_PROC_BROWSER_TEST_F(ComposeTest, App) {
+INSTANTIATE_TEST_SUITE_P(/*no prefix*/,
+                         ComposeTest,
+                         ::testing::Bool(),
+                         ComposeTest::DescribeParams);
+
+IN_PROC_BROWSER_TEST_P(ComposeTest, App) {
   RunTest("compose/compose_app_test.js", "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(ComposeTest, Textarea) {
+IN_PROC_BROWSER_TEST_P(ComposeTest, Textarea) {
   RunTest("compose/compose_textarea_test.js", "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(ComposeTest, Animator) {
+IN_PROC_BROWSER_TEST_P(ComposeTest, Animator) {
   RunTest("compose/compose_animator_test.js", "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(ComposeTest, WordStreamer) {
+IN_PROC_BROWSER_TEST_P(ComposeTest, WordStreamer) {
   RunTest("compose/word_streamer_test.js", "mocha.run()");
 }
 
-IN_PROC_BROWSER_TEST_F(ComposeTest, ResultText) {
+IN_PROC_BROWSER_TEST_P(ComposeTest, ResultText) {
   RunTest("compose/result_text_test.js", "mocha.run()");
 }
diff --git a/chrome/test/data/webui/cr_elements/cr_dialog_test.ts b/chrome/test/data/webui/cr_elements/cr_dialog_test.ts
index 6a3d9b7..58b11137 100644
--- a/chrome/test/data/webui/cr_elements/cr_dialog_test.ts
+++ b/chrome/test/data/webui/cr_elements/cr_dialog_test.ts
@@ -10,8 +10,8 @@
 import type {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
 import type {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
 import {keyDownOn, keyEventOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
-import {assertEquals, assertFalse, assertNotEquals, assertNotReached, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {eventToPromise} from 'chrome://webui-test/test_util.js';
+import {assertEquals, assertFalse, assertNotEquals, assertNotReached, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
 // clang-format on
@@ -174,7 +174,7 @@
 
   test('enter keys should trigger action buttons once', function() {
     document.body.innerHTML = getTrustedHTML`
-      <cr-dialog>
+      <cr-dialog show-close-button>
         <div slot="title">title</div>
         <div slot="body">
           <button class="action-button">button</button>
@@ -213,7 +213,9 @@
     assertEquals(0, clickedCounter);
 
     // Enter keys on the close icon in the top-right corner should be ignored.
-    pressEnter(dialog.$.close);
+    const close = dialog.shadowRoot!.querySelector<HTMLElement>('#close');
+    assertTrue(!!close);
+    pressEnter(close);
     assertEquals(0, clickedCounter);
   });
 
@@ -449,7 +451,7 @@
     assertTrue(dialog.noCancel);
     dialog.showModal();
 
-    assertTrue(dialog.$.close.hidden);
+    assertNull(dialog.shadowRoot!.querySelector('#close'));
 
     // Hitting escape fires a 'cancel' event. Cancelling that event prevents the
     // dialog from closing.
@@ -475,9 +477,10 @@
     dialog.showModal();
     assertTrue(dialog.open);
 
-    assertFalse(dialog.$.close.hidden);
-    assertEquals('flex', window.getComputedStyle(dialog.$.close).display);
-    dialog.$.close.click();
+    const close = dialog.shadowRoot!.querySelector<HTMLElement>('#close');
+    assertTrue(!!close);
+    assertTrue(isVisible(close));
+    close.click();
     assertFalse(dialog.open);
   });
 
@@ -490,8 +493,7 @@
     const dialog = document.body.querySelector('cr-dialog')!;
     dialog.showModal();
 
-    assertTrue(dialog.$.close.hidden);
-    assertEquals('none', window.getComputedStyle(dialog.$.close).display);
+    assertNull(dialog.shadowRoot!.querySelector('#close'));
   });
 
   test('keydown should be consumed when the property is true', function() {
@@ -553,20 +555,22 @@
 
   test('close-text', async () => {
     document.body.innerHTML = getTrustedHTML`
-      <cr-dialog close-text="foo">
+      <cr-dialog close-text="foo" show-close-button>
         <div slot="title">title</div>
       </cr-dialog>`;
     const dialog = document.body.querySelector('cr-dialog')!;
     dialog.showModal();
 
     assertEquals('foo', dialog.closeText);
-    assertEquals('foo', dialog.$.close.ariaLabel);
-    assertEquals('foo', dialog.$.close.getAttribute('aria-label'));
+    const close = dialog.shadowRoot!.querySelector<HTMLElement>('#close');
+    assertTrue(!!close);
+    assertEquals('foo', close.ariaLabel);
+    assertEquals('foo', close.getAttribute('aria-label'));
 
     dialog.closeText = undefined;
     await dialog.updateComplete;
-    assertEquals(null, dialog.$.close.ariaLabel);
-    assertFalse(dialog.$.close.hasAttribute('aria-label'));
+    assertEquals(null, close.ariaLabel);
+    assertFalse(close.hasAttribute('aria-label'));
   });
 
   // Test that when ignoreEnterKey is set, pressing "Enter" does not trigger the
diff --git a/chrome/test/data/webui/cr_elements/cr_url_list_item_test.ts b/chrome/test/data/webui/cr_elements/cr_url_list_item_test.ts
index 69340130..fa7f01e9 100644
--- a/chrome/test/data/webui/cr_elements/cr_url_list_item_test.ts
+++ b/chrome/test/data/webui/cr_elements/cr_url_list_item_test.ts
@@ -183,6 +183,15 @@
         element.$.anchor.getBoundingClientRect());
   });
 
+  test('SetsTargetForAnchor', async () => {
+    element.asAnchor = true;
+    await element.updateComplete;
+    assertEquals('_self', element.$.anchor.target);
+    element.asAnchorTarget = '_blank';
+    await element.updateComplete;
+    assertEquals('_blank', element.$.anchor.target);
+  });
+
   test('PassesAriaProperties', async () => {
     element.title = 'My title';
     element.description = 'My description';
diff --git a/chrome/test/data/webui/lens/lens_webui_browsertest.cc b/chrome/test/data/webui/lens/lens_webui_browsertest.cc
index a973bbaa..8ae67b0 100644
--- a/chrome/test/data/webui/lens/lens_webui_browsertest.cc
+++ b/chrome/test/data/webui/lens/lens_webui_browsertest.cc
@@ -6,6 +6,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/lens/lens_overlay_controller.h"
+#include "chrome/browser/ui/lens/lens_overlay_invocation_source.h"
 #include "chrome/browser/ui/lens/lens_overlay_permission_utils.h"
 #include "chrome/browser/ui/tabs/tab_features.h"
 #include "chrome/common/webui_url_constants.h"
@@ -75,7 +76,7 @@
     ASSERT_EQ(controller->state(), State::kOff);
 
     // Showing UI should eventually result in overlay state.
-    controller->ShowUI(LensOverlayController::InvocationSource::kAppMenu);
+    controller->ShowUI(lens::LensOverlayInvocationSource::kAppMenu);
     ASSERT_TRUE(base::test::RunUntil(
         [&]() { return controller->state() == State::kOverlay; }));
 
diff --git a/chrome/test/fuzzing/in_process_fuzzer.cc b/chrome/test/fuzzing/in_process_fuzzer.cc
index 53d907e..2f3e91f 100644
--- a/chrome/test/fuzzing/in_process_fuzzer.cc
+++ b/chrome/test/fuzzing/in_process_fuzzer.cc
@@ -270,13 +270,7 @@
         fuzzer->GetChromiumCommandLineArguments();
     chromium_arguments.insert(chromium_arguments.begin(), executable_name);
     chromium_arguments.push_back(FILE_PATH_LITERAL("--single-process-tests"));
-#if BUILDFLAG(IS_CENTIPEDE)
-    // TODO(crbug.com/40051117): make libfuzzer compatible with single-process
-    // mode. As it stands, single-process mode works with centipede (and is
-    // probably desirable both in terms of fuzzing speed and correctly gathering
-    // coverage information) but not yet with libfuzzer.
     chromium_arguments.push_back(FILE_PATH_LITERAL("--single-process"));
-#endif  // BUILDFLAG(IS_CENTIPEDE)
     chromium_arguments.push_back(FILE_PATH_LITERAL("--no-sandbox"));
     chromium_arguments.push_back(FILE_PATH_LITERAL("--no-zygote"));
     chromium_arguments.push_back(FILE_PATH_LITERAL("--disable-gpu"));
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index 2f2bd64e..5b6079b 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -937,6 +937,7 @@
         ":ks_ticket",
         "//chrome/updater/mac:ksadmin_lib",
         "//chrome/updater/mac:privileged_helper_sources",
+        "//chrome/updater/mac/client_lib:test_sources",
         "//third_party/ocmock",
       ]
 
diff --git a/chrome/updater/mac/client_lib/BUILD.gn b/chrome/updater/mac/client_lib/BUILD.gn
new file mode 100644
index 0000000..bb703d8e
--- /dev/null
+++ b/chrome/updater/mac/client_lib/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//testing/test.gni")
+
+source_set("public_sources") {
+  sources = [
+    "CRURegistration.h",
+    "CRURegistration.m",
+  ]
+  assert_no_deps = [ "*" ]
+}
+
+source_set("test_sources") {
+  testonly = true
+  sources = [ "CRURegistration_unittests.mm" ]
+  deps = [
+    ":public_sources",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/updater/mac/client_lib/CRURegistration.h b/chrome/updater/mac/client_lib/CRURegistration.h
new file mode 100644
index 0000000..dddc9239
--- /dev/null
+++ b/chrome/updater/mac/client_lib/CRURegistration.h
@@ -0,0 +1,71 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_UPDATER_MAC_CLIENT_LIB_CRUREGISTRATION_H_
+#define CHROME_UPDATER_MAC_CLIENT_LIB_CRUREGISTRATION_H_
+
+#import <Foundation/Foundation.h>
+#import <dispatch/dispatch.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * CRURegistration interfaces with Chromium Updater to configure and retrieve
+ * information about an app, or to install the updater for the current user. Its
+ * methods can be invoked from any thread or queue.
+ *
+ * Do not block CRURegistration's target queue synchronously waiting for a
+ * callback from CRURegistration; this causes deadlock. Invoking CRURegistration
+ * methods on this (or any) queue without subsequently synchronously waiting for
+ * a provided callback to execute is safe. CRURegistration does not block its
+ * target queue.
+ */
+@interface CRURegistration : NSObject
+
+/**
+ * Initializes a CRURegistration instance to manage Chromium Updater's
+ * information about the app with the provided ID, using a specified queue
+ * for execution and callbacks. This queue can be serial or concurrent, but
+ * typically should not be the main queue.
+ *
+ * @param appId The ID of the app this CRURegistration instance operates on.
+ * @param targetQueue Dispatch queue for callbacks and internal operations.
+ *     If this queue is blocked, CRURegistration will get stuck.
+ */
+- (instancetype)initWithAppId:(NSString*)appId
+                  targetQueue:(dispatch_queue_t)targetQueue
+    NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Initializes a CRURegistration instance to manage Chromium Updater's
+ * information about the app with the provided ID, using a global concurrent
+ * queue for execution (with the specified quality of service).
+ *
+ * @param appId The ID of the app this CRURegistration instance operates on.
+ * @param qos Identifier for the global concurrent queue to use for callbacks
+ *     and internal operations. See Apple's documentation for
+ *     `dispatch_get_global_queue` for more details:
+ *     https://developer.apple.com/documentation/dispatch/1452927-dispatch_get_global_queue
+ */
+- (instancetype)initWithAppId:(NSString*)appId qos:(dispatch_qos_class_t)qos;
+
+/**
+ * Initializes a CRURegistration instance to manage Chromium Updater's
+ * information about the app with the provided ID, using the `QOS_CLASS_UTILITY`
+ * global concurrent queue for execution.
+ *
+ * @param appId The ID of the app this CRURegistration instance operates on.
+ */
+- (instancetype)initWithAppId:(NSString*)appId;
+
+/**
+ * CRURegistration cannot be initialized without an app ID.
+ */
+- (instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif  // CHROME_UPDATER_MAC_CLIENT_LIB_CRUREGISTRATION_H_
diff --git a/chrome/updater/mac/client_lib/CRURegistration.m b/chrome/updater/mac/client_lib/CRURegistration.m
new file mode 100644
index 0000000..3f1d96c
--- /dev/null
+++ b/chrome/updater/mac/client_lib/CRURegistration.m
@@ -0,0 +1,38 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "CRURegistration.h"
+
+#import <Foundation/Foundation.h>
+#import <dispatch/dispatch.h>
+
+@implementation CRURegistration {
+  // Immutable fields.
+  NSString* _appId;
+
+  dispatch_queue_t _privateQueue;
+  dispatch_queue_t _parentQueue;
+}
+
+- (instancetype)initWithAppId:(NSString*)appId
+                  targetQueue:(dispatch_queue_t)targetQueue {
+  if (self = [super init]) {
+    _appId = appId;
+    _parentQueue = targetQueue;
+    _privateQueue = dispatch_queue_create_with_target(
+        "CRURegistration", DISPATCH_QUEUE_SERIAL, targetQueue);
+  }
+  return self;
+}
+
+- (instancetype)initWithAppId:(NSString*)appId qos:(dispatch_qos_class_t)qos {
+  return [self initWithAppId:appId
+                 targetQueue:dispatch_get_global_queue(qos, 0)];
+}
+
+- (instancetype)initWithAppId:(NSString*)appId {
+  return [self initWithAppId:appId qos:QOS_CLASS_UTILITY];
+}
+
+@end
diff --git a/chrome/updater/mac/client_lib/CRURegistration_unittests.mm b/chrome/updater/mac/client_lib/CRURegistration_unittests.mm
new file mode 100644
index 0000000..9beec2ba
--- /dev/null
+++ b/chrome/updater/mac/client_lib/CRURegistration_unittests.mm
@@ -0,0 +1,20 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/updater/mac/client_lib/CRURegistration.h"
+
+#import <Foundation/Foundation.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(CRURegistrationTest, SmokeTest) {
+  CRURegistration* registration = [[CRURegistration alloc]
+      initWithAppId:
+          @"org.chromium.ChromiumUpdater.CRURegistrationTest.SmokeTest"];
+  ASSERT_TRUE(registration);
+}
+
+}  // namespace
diff --git a/chrome/updater/mac/setup/ks_tickets.mm b/chrome/updater/mac/setup/ks_tickets.mm
index 0724d70..8fab477 100644
--- a/chrome/updater/mac/setup/ks_tickets.mm
+++ b/chrome/updater/mac/setup/ks_tickets.mm
@@ -55,12 +55,12 @@
     return nil;
   }
   unpacker.requiresSecureCoding = YES;
-  NSSet* classes =
-      [NSSet setWithObjects:[NSDictionary class], [KSTicket class],
-                            [KSPathExistenceChecker class],
-                            [KSLaunchServicesExistenceChecker class],
-                            [KSSpotlightExistenceChecker class],
-                            [NSArray class], [NSSet class], [NSURL class], nil];
+  NSSet* classes = [NSSet
+      setWithObjects:[NSDictionary class], [KSTicket class],
+                     [KSPathExistenceChecker class],
+                     [KSLaunchServicesExistenceChecker class],
+                     [KSSpotlightExistenceChecker class], [NSArray class],
+                     [NSSet class], [NSURL class], [NSString class], nil];
   store = [unpacker decodeObjectOfClasses:classes
                                    forKey:NSKeyedArchiveRootObjectKey];
   [unpacker finishDecoding];
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 46a911d..20d415e 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-15889.0.0
\ No newline at end of file
+15890.0.0
\ No newline at end of file
diff --git a/chromeos/ash/components/system/statistics_provider.cc b/chromeos/ash/components/system/statistics_provider.cc
index 4a9b3e3..cc2f845 100644
--- a/chromeos/ash/components/system/statistics_provider.cc
+++ b/chromeos/ash/components/system/statistics_provider.cc
@@ -4,6 +4,7 @@
 
 #include "chromeos/ash/components/system/statistics_provider.h"
 
+#include <array>
 #include <string_view>
 
 #include "base/memory/singleton.h"
@@ -29,66 +30,13 @@
 // some kevin devices and thus needs to be supported until AUE of these
 // devices. It's known *not* to be present on caroline.
 // TODO(tnagel): Remove "Product_S/N" after all devices that have it are AUE.
-const char* const kMachineInfoSerialNumberKeys[] = {
+constexpr std::array kMachineInfoSerialNumberKeys = {
     kSerialNumberKey,        // VPD v2+ devices (Samsung: caroline and later)
     kFlexIdKey,              // Used by Reven devices
     kLegacySerialNumberKey,  // Samsung legacy
 };
 }  // namespace
 
-// Key values for `GetMachineStatistic()`/`GetMachineFlag()` calls.
-const char kActivateDateKey[] = "ActivateDate";
-const char kBlockDevModeKey[] = "block_devmode";
-const char kCheckEnrollmentKey[] = "check_enrollment";
-const char kShouldSendRlzPingKey[] = "should_send_rlz_ping";
-const char kShouldSendRlzPingValueFalse[] = "0";
-const char kShouldSendRlzPingValueTrue[] = "1";
-const char kRlzEmbargoEndDateKey[] = "rlz_embargo_end_date";
-const char kEnterpriseManagementEmbargoEndDateKey[] =
-    "enterprise_management_embargo_end_date";
-const char kCustomizationIdKey[] = "customization_id";
-const char kDevSwitchBootKey[] = "devsw_boot";
-const char kDevSwitchBootValueDev[] = "1";
-const char kDevSwitchBootValueVerified[] = "0";
-const char kDockMacAddressKey[] = "dock_mac";
-const char kEthernetMacAddressKey[] = "ethernet_mac0";
-const char kFirmwareWriteProtectCurrentKey[] = "wpsw_cur";
-const char kFirmwareWriteProtectCurrentValueOn[] = "1";
-const char kFirmwareWriteProtectCurrentValueOff[] = "0";
-const char kFirmwareTypeKey[] = "mainfw_type";
-const char kFirmwareTypeValueDeveloper[] = "developer";
-const char kFirmwareTypeValueNonchrome[] = "nonchrome";
-const char kFirmwareTypeValueNormal[] = "normal";
-const char kHardwareClassKey[] = "hardware_class";
-const char kIsVmKey[] = "is_vm";
-const char kIsVmValueFalse[] = "0";
-const char kIsVmValueTrue[] = "1";
-const char kIsCrosDebugKey[] = "is_cros_debug";
-const char kIsCrosDebugValueFalse[] = "0";
-const char kIsCrosDebugValueTrue[] = "1";
-const char kMachineModelName[] = "model_name";
-const char kMachineOemName[] = "oem_name";
-const char kManufactureDateKey[] = "mfg_date";
-const char kOffersCouponCodeKey[] = "ubind_attribute";
-const char kOffersGroupCodeKey[] = "gbind_attribute";
-const char kRlzBrandCodeKey[] = "rlz_brand_code";
-const char kRegionKey[] = "region";
-const char kSerialNumberKey[] = "serial_number";
-const char kLegacySerialNumberKey[] = "Product_S/N";
-const char kFlexIdKey[] = "flex_id";
-const char kInitialLocaleKey[] = "initial_locale";
-const char kInitialTimezoneKey[] = "initial_timezone";
-const char kKeyboardLayoutKey[] = "keyboard_layout";
-const char kKeyboardMechanicalLayoutKey[] = "keyboard_mechanical_layout";
-const char kAttestedDeviceIdKey[] = "attested_device_id";
-const char kDisplayProfilesKey[] = "display_profiles";
-
-// OEM specific statistics. Must be prefixed with "oem_".
-const char kOemCanExitEnterpriseEnrollmentKey[] = "oem_can_exit_enrollment";
-const char kOemDeviceRequisitionKey[] = "oem_device_requisition";
-const char kOemIsEnterpriseManagedKey[] = "oem_enterprise_managed";
-const char kOemKeyboardDrivenOobeKey[] = "oem_keyboard_driven_oobe";
-
 // The StatisticsProvider implementation used in production.
 class StatisticsProviderSingleton final : public StatisticsProviderImpl {
  public:
diff --git a/chromeos/ash/components/system/statistics_provider.h b/chromeos/ash/components/system/statistics_provider.h
index bcd7f42b..19593d6 100644
--- a/chromeos/ash/components/system/statistics_provider.h
+++ b/chromeos/ash/components/system/statistics_provider.h
@@ -14,174 +14,131 @@
 namespace ash::system {
 
 // Activation date key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kActivateDateKey[];
+inline constexpr char kActivateDateKey[] = "ActivateDate";
 
 // The key that will be present in VPD if the device was enrolled in a domain
 // that blocks dev mode.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kBlockDevModeKey[];
+inline constexpr char kBlockDevModeKey[] = "block_devmode";
 // The key that will be present in VPD if the device ever was enrolled.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kCheckEnrollmentKey[];
+inline constexpr char kCheckEnrollmentKey[] = "check_enrollment";
 
 // The key and values present in VPD to indicate if RLZ ping should be sent.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kShouldSendRlzPingKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kShouldSendRlzPingValueFalse[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kShouldSendRlzPingValueTrue[];
+inline constexpr char kShouldSendRlzPingKey[] = "should_send_rlz_ping";
+inline constexpr char kShouldSendRlzPingValueFalse[] = "0";
+inline constexpr char kShouldSendRlzPingValueTrue[] = "1";
 
 // The key present in VPD that indicates the date after which the RLZ ping is
 // allowed to be sent. It is in the format of "yyyy-mm-dd".
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kRlzEmbargoEndDateKey[];
+inline constexpr char kRlzEmbargoEndDateKey[] = "rlz_embargo_end_date";
 
 // The key present in VPD that indicates the date after which enterprise
 // management pings are allowed to be sent. It is in the format of "yyyy-mm-dd".
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kEnterpriseManagementEmbargoEndDateKey[];
+inline constexpr char kEnterpriseManagementEmbargoEndDateKey[] =
+    "enterprise_management_embargo_end_date";
 
 // Customization ID key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kCustomizationIdKey[];
+inline constexpr char kCustomizationIdKey[] = "customization_id";
 
 // Developer switch value.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kDevSwitchBootKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kDevSwitchBootValueDev[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kDevSwitchBootValueVerified[];
+inline constexpr char kDevSwitchBootKey[] = "devsw_boot";
+inline constexpr char kDevSwitchBootValueDev[] = "1";
+inline constexpr char kDevSwitchBootValueVerified[] = "0";
 
 // Dock MAC address key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kDockMacAddressKey[];
+inline constexpr char kDockMacAddressKey[] = "dock_mac";
 
 // Ethernet MAC address key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kEthernetMacAddressKey[];
+inline constexpr char kEthernetMacAddressKey[] = "ethernet_mac0";
 
 // Firmware write protect switch value.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kFirmwareWriteProtectCurrentKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kFirmwareWriteProtectCurrentValueOn[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kFirmwareWriteProtectCurrentValueOff[];
+inline constexpr char kFirmwareWriteProtectCurrentKey[] = "wpsw_cur";
+inline constexpr char kFirmwareWriteProtectCurrentValueOn[] = "1";
+inline constexpr char kFirmwareWriteProtectCurrentValueOff[] = "0";
 
 // Firmware type and associated values. The values are from crossystem output
 // for the mainfw_type key. Normal and developer correspond to Chrome OS
 // firmware with MP and developer keys respectively, nonchrome indicates the
 // machine doesn't run on Chrome OS firmware. See crossystem source for more
 // details.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kFirmwareTypeKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kFirmwareTypeValueDeveloper[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kFirmwareTypeValueNonchrome[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kFirmwareTypeValueNormal[];
+inline constexpr char kFirmwareTypeKey[] = "mainfw_type";
+inline constexpr char kFirmwareTypeValueDeveloper[] = "developer";
+inline constexpr char kFirmwareTypeValueNonchrome[] = "nonchrome";
+inline constexpr char kFirmwareTypeValueNormal[] = "normal";
 
 // HWID key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kHardwareClassKey[];
+inline constexpr char kHardwareClassKey[] = "hardware_class";
 
 // Key/values reporting if Chrome OS is running in a VM or not. These values are
 // read from crossystem output. See crossystem source for VM detection logic.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM) extern const char kIsVmKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kIsVmValueFalse[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kIsVmValueTrue[];
+inline constexpr char kIsVmKey[] = "is_vm";
+inline constexpr char kIsVmValueFalse[] = "0";
+inline constexpr char kIsVmValueTrue[] = "1";
 
 // Key/values reporting if ChromeOS is running in debug mode or not. These
 // values are read from crossystem output. See crossystem source for cros_debug
 // detection logic.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kIsCrosDebugKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kIsCrosDebugValueFalse[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kIsCrosDebugValueTrue[];
+inline constexpr char kIsCrosDebugKey[] = "is_cros_debug";
+inline constexpr char kIsCrosDebugValueFalse[] = "0";
+inline constexpr char kIsCrosDebugValueTrue[] = "1";
 
 // Manufacture date key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kManufactureDateKey[];
+inline constexpr char kManufactureDateKey[] = "mfg_date";
 
 // OEM customization flag that permits exiting enterprise enrollment flow in
 // OOBE when 'oem_enterprise_managed' flag is set.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kOemCanExitEnterpriseEnrollmentKey[];
+inline constexpr char kOemCanExitEnterpriseEnrollmentKey[] =
+    "oem_can_exit_enrollment";
 
 // OEM customization directive that specified intended device purpose.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kOemDeviceRequisitionKey[];
+inline constexpr char kOemDeviceRequisitionKey[] = "oem_device_requisition";
 
 // OEM customization flag that enforces enterprise enrollment flow in OOBE.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kOemIsEnterpriseManagedKey[];
+inline constexpr char kOemIsEnterpriseManagedKey[] = "oem_enterprise_managed";
 
 // OEM customization flag that specifies if OOBE flow should be enhanced for
 // keyboard driven control.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kOemKeyboardDrivenOobeKey[];
+inline constexpr char kOemKeyboardDrivenOobeKey[] = "oem_keyboard_driven_oobe";
 
 // Offer coupon code key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kOffersCouponCodeKey[];
+inline constexpr char kOffersCouponCodeKey[] = "ubind_attribute";
 
 // Offer group key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kOffersGroupCodeKey[];
+inline constexpr char kOffersGroupCodeKey[] = "gbind_attribute";
 
 // Release Brand Code key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kRlzBrandCodeKey[];
+inline constexpr char kRlzBrandCodeKey[] = "rlz_brand_code";
 
 // Regional data
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM) extern const char kRegionKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kInitialLocaleKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kInitialTimezoneKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kKeyboardLayoutKey[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kKeyboardMechanicalLayoutKey[];
+inline constexpr char kRegionKey[] = "region";
+inline constexpr char kInitialLocaleKey[] = "initial_locale";
+inline constexpr char kInitialTimezoneKey[] = "initial_timezone";
+inline constexpr char kKeyboardLayoutKey[] = "keyboard_layout";
+inline constexpr char kKeyboardMechanicalLayoutKey[] =
+    "keyboard_mechanical_layout";
 
 // The key that will be present in RO VPD to indicate what identifier is used
 // for attestation-based registration of a device.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kAttestedDeviceIdKey[];
+inline constexpr char kAttestedDeviceIdKey[] = "attested_device_id";
 
 // Serial number key (legacy VPD devices). In most cases,
 // GetEnterpriseMachineID() is the appropriate way to obtain the serial number.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kLegacySerialNumberKey[];
+inline constexpr char kLegacySerialNumberKey[] = "Product_S/N";
 
 // Serial number key (VPD v2+ devices, Samsung: caroline and later). In most
 // cases, GetEnterpriseMachineID() is the appropriate way to obtain the serial
 // number.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kSerialNumberKey[];
+inline constexpr char kSerialNumberKey[] = "serial_number";
 
 // Serial number key for Flex devices. In most cases, GetEnterpriseMachineID()
 // is the appropriate way to obtain the serial number.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kFlexIdKey[];
+inline constexpr char kFlexIdKey[] = "flex_id";
 
 // Display Profiles key.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kDisplayProfilesKey[];
+inline constexpr char kDisplayProfilesKey[] = "display_profiles";
 
 // Machine model and oem names.
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kMachineModelName[];
-COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM)
-extern const char kMachineOemName[];
+inline constexpr char kMachineModelName[] = "model_name";
+inline constexpr char kMachineOemName[] = "oem_name";
 
 // This interface provides access to Chrome OS statistics.
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_SYSTEM) StatisticsProvider {
diff --git a/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc b/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc
index ea91870..43a2bf8 100644
--- a/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc
+++ b/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc
@@ -15,7 +15,6 @@
 #include "base/task/bind_post_task.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
-#include "base/unguessable_token.h"
 #include "chromeos/components/cdm_factory_daemon/cdm_storage_adapter.h"
 #include "chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h"
 #include "chromeos/components/cdm_factory_daemon/mojom/content_decryption_module.mojom.h"
@@ -159,6 +158,25 @@
 };
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+void OnCdmCreated(media::CdmCreatedCB callback,
+                  scoped_refptr<ContentDecryptionModuleAdapter> cdm,
+                  cdm::mojom::CdmFactory::CreateCdmStatus result) {
+  std::string err;
+  switch (result) {
+    case cdm::mojom::CdmFactory::CreateCdmStatus::kSuccess:
+      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(callback), std::move(cdm), ""));
+      return;
+    case cdm::mojom::CdmFactory::CreateCdmStatus::kNoMoreInstances:
+      err = "Only one instance allowed";
+      break;
+    case cdm::mojom::CdmFactory::CreateCdmStatus::kInsufficientGpuResources:
+      err = "Insufficient GPU memory available";
+      break;
+  }
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), nullptr, err));
+}
 }  // namespace
 
 ChromeOsCdmFactory::ChromeOsCdmFactory(
@@ -387,15 +405,15 @@
           &GetOutputProtectionOnTaskRunner,
           output_protection_remote.InitWithNewPipeAndPassReceiver()));
 
-  // Now create the remote CDM instance that links everything up.
-  remote_factory_->CreateCdm(cdm->GetClientInterface(),
-                             std::move(storage_remote),
-                             std::move(output_protection_remote),
-                             base::UnguessableToken::Create().ToString(),
-                             std::move(cros_cdm_pending_receiver));
+  url::Origin cdm_origin;
+  frame_interfaces_->GetCdmOrigin(&cdm_origin);
 
-  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(cdm_created_cb), std::move(cdm), ""));
+  // Now create the remote CDM instance that links everything up.
+  remote_factory_->CreateCdm(
+      cdm->GetClientInterface(), std::move(storage_remote),
+      std::move(output_protection_remote), cdm_origin.host(),
+      std::move(cros_cdm_pending_receiver),
+      base::BindOnce(&OnCdmCreated, std::move(cdm_created_cb), std::move(cdm)));
 }
 
 void ChromeOsCdmFactory::OnFactoryMojoConnectionError() {
diff --git a/chromeos/components/cdm_factory_daemon/mojom/cdm_factory_daemon.mojom b/chromeos/components/cdm_factory_daemon/mojom/cdm_factory_daemon.mojom
index cef17c4..bf6d4dd 100644
--- a/chromeos/components/cdm_factory_daemon/mojom/cdm_factory_daemon.mojom
+++ b/chromeos/components/cdm_factory_daemon/mojom/cdm_factory_daemon.mojom
@@ -9,7 +9,7 @@
 // interface can also be used to connect directly to the OEMCrypto
 // implementation for ARC.
 
-// Next MinVersion: 9
+// Next MinVersion: 10
 
 module chromeos.cdm.mojom;
 
@@ -26,22 +26,39 @@
 interface CdmFactory {
   // Deprecated, do not use.
   [MinVersion=1]
-  CreateCdmDeprecated@1(
-              pending_associated_remote<ContentDecryptionModuleClient> client,
-              pending_associated_remote<CdmStorage> storage,
-              pending_associated_receiver<ContentDecryptionModule> cdm,
-              pending_remote<OutputProtection> output_protection);
+  DEPRECATED_1@1(
+      pending_associated_remote<ContentDecryptionModuleClient> client,
+      pending_associated_remote<CdmStorage> storage,
+      pending_associated_receiver<ContentDecryptionModule> cdm,
+      pending_remote<OutputProtection> output_protection);
+
+  // Deprecated, do not use.
+  [MinVersion=3]
+  CreateCdmDeprecated@2(
+      pending_associated_remote<ContentDecryptionModuleClient> client,
+      pending_associated_remote<CdmStorage> storage,
+      pending_remote<OutputProtection> output_protection,
+      string host,
+      pending_associated_receiver<ContentDecryptionModule> cdm);
+
+  [Stable, Extensible]
+  enum CreateCdmStatus {
+    [Default] kSuccess,
+    kNoMoreInstances,
+    kInsufficientGpuResources,
+  };
 
   // Creates a new ContentDecryptionModule instance for a |host| with the
   // corresponding |client|, remote |storage| implementation and
   // |output_protection|. Use an associated interface to ensure ordering and
   // that all become invalidated at the same time.
-  [MinVersion=3]
-  CreateCdm@2(pending_associated_remote<ContentDecryptionModuleClient> client,
+  [MinVersion=9]
+  CreateCdm@3(pending_associated_remote<ContentDecryptionModuleClient> client,
               pending_associated_remote<CdmStorage> storage,
               pending_remote<OutputProtection> output_protection,
               string host,
-              pending_associated_receiver<ContentDecryptionModule> cdm);
+              pending_associated_receiver<ContentDecryptionModule> cdm) =>
+      (CreateCdmStatus result);
 };
 
 // Next Method ID: 10
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 7549c47..69407c15 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -200,6 +200,9 @@
              "KioskHeartbeatsViaERP",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Controls enabling / disabling the Magic Boost feature.
+BASE_FEATURE(kMagicBoost, "MagicBoost", base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Controls enabling / disabling the mahi feature.
 BASE_FEATURE(kMahi, "Mahi", base::FEATURE_DISABLED_BY_DEFAULT);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
@@ -467,6 +470,14 @@
   return IsJellyEnabled() && base::FeatureList::IsEnabled(kJellyroll);
 }
 
+bool IsMagicBoostEnabled() {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  return chromeos::BrowserParamsProxy::Get()->IsMagicBoostEnabled();
+#else
+  return base::FeatureList::IsEnabled(kMagicBoost);
+#endif
+}
+
 bool IsMahiEnabled() {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   return chromeos::BrowserParamsProxy::Get()->IsMahiEnabled();
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 58c11806..ca1a7db 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -73,6 +73,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 BASE_DECLARE_FEATURE(kKioskHeartbeatsViaERP);
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS) BASE_DECLARE_FEATURE(kMagicBoost);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) BASE_DECLARE_FEATURE(kMahi);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) BASE_DECLARE_FEATURE(kSparky);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
@@ -141,6 +142,7 @@
 bool IsFileSystemProviderContentCacheEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsJellyEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsJellyrollEnabled();
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsMagicBoostEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsMahiEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsMahiDebuggingEnabled();
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) bool IsRoundedWindowsEnabled();
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index c508ad4b..a268772 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -1152,8 +1152,8 @@
 // parameters here. If a new parameter is added and its value is only known
 // after the user has logged in, please update BrowserPostLoginParams as well.
 //
-// Next version: 89
-// Next id: 89
+// Next version: 90
+// Next id: 90
 [Stable, RenamedFrom="crosapi.mojom.LacrosInitParams"]
 struct BrowserInitParams {
   // This is ash-chrome's version of the Crosapi interface. This is used by
@@ -1659,6 +1659,10 @@
   // Set to true when orca internationalization flag is enabled in Ash.
   [MinVersion=88]
   bool is_orca_internationalize_enabled@88;
+
+  // If true, "Magic Boost" will be enabled.
+  [MinVersion=89]
+  bool is_magic_boost_enabled@89;
 };
 
 // BrowserPostLoginParams is the subset of parameters in BrowserInitParams
diff --git a/chromeos/startup/browser_params_proxy.cc b/chromeos/startup/browser_params_proxy.cc
index 24e8b95..e45f743 100644
--- a/chromeos/startup/browser_params_proxy.cc
+++ b/chromeos/startup/browser_params_proxy.cc
@@ -381,6 +381,10 @@
   return BrowserInitParams::Get()->is_cros_mall_enabled;
 }
 
+bool BrowserParamsProxy::IsMagicBoostEnabled() const {
+  return BrowserInitParams::Get()->is_magic_boost_enabled;
+}
+
 bool BrowserParamsProxy::IsMahiEnabled() const {
   return BrowserInitParams::Get()->is_mahi_enabled;
 }
diff --git a/chromeos/startup/browser_params_proxy.h b/chromeos/startup/browser_params_proxy.h
index 0f2580b..1a69686 100644
--- a/chromeos/startup/browser_params_proxy.h
+++ b/chromeos/startup/browser_params_proxy.h
@@ -176,6 +176,8 @@
 
   bool IsCrosMallEnabled() const;
 
+  bool IsMagicBoostEnabled() const;
+
   bool IsMahiEnabled() const;
 
   bool IsContainerAppPreinstallEnabled() const;
diff --git a/chromeos/ui/frame/interior_resize_handler_targeter.cc b/chromeos/ui/frame/interior_resize_handler_targeter.cc
index ea60b3e..5e7c349 100644
--- a/chromeos/ui/frame/interior_resize_handler_targeter.cc
+++ b/chromeos/ui/frame/interior_resize_handler_targeter.cc
@@ -9,7 +9,9 @@
 
 namespace chromeos {
 
-InteriorResizeHandleTargeter::InteriorResizeHandleTargeter() {
+InteriorResizeHandleTargeter::InteriorResizeHandleTargeter(
+    WindowStateTypeCallback window_state_type_cb)
+    : window_state_type_cb_(std::move(window_state_type_cb)) {
   SetInsets(gfx::Insets(chromeos::kResizeInsideBoundsSize));
 }
 
@@ -35,10 +37,12 @@
 
 bool InteriorResizeHandleTargeter::ShouldUseExtendedBounds(
     const aura::Window* target) const {
-  // Fullscreen/maximized/pinned windows can't be drag-resized.
-  // TODO(crbug.com/40143671): Incorporate the check in
-  // InteriorResizeHandleTargeterAsh::ShouldUseExtendedBounds() override here.
-  //
+  // Fullscreen/maximized windows can't be drag-resized.
+  if (IsMaximizedOrFullscreenOrPinnedWindowStateType(
+          window_state_type_cb_.Run(window()))) {
+    return false;
+  }
+
   // The shrunken hit region only applies to children of |window()|.
   return target->parent() == window();
 }
diff --git a/chromeos/ui/frame/interior_resize_handler_targeter.h b/chromeos/ui/frame/interior_resize_handler_targeter.h
index 4cc7362..fdb6ed8 100644
--- a/chromeos/ui/frame/interior_resize_handler_targeter.h
+++ b/chromeos/ui/frame/interior_resize_handler_targeter.h
@@ -5,15 +5,22 @@
 #ifndef CHROMEOS_UI_FRAME_INTERIOR_RESIZE_HANDLER_TARGETER_H_
 #define CHROMEOS_UI_FRAME_INTERIOR_RESIZE_HANDLER_TARGETER_H_
 
+#include "base/functional/callback.h"
+#include "chromeos/ui/base/window_state_type.h"
 #include "ui/aura/window_targeter.h"
 
 namespace chromeos {
 
 // This window targeter reserves space for the portion of the resize handles
-// that extend within a top level window.
+// that extend within a top level window. This targeter is should only be
+// installed on frame windows.
 class InteriorResizeHandleTargeter : public aura::WindowTargeter {
  public:
-  InteriorResizeHandleTargeter();
+  using WindowStateTypeCallback =
+      base::RepeatingCallback<WindowStateType(const aura::Window*)>;
+
+  explicit InteriorResizeHandleTargeter(
+      WindowStateTypeCallback window_state_type_cb);
   InteriorResizeHandleTargeter(const InteriorResizeHandleTargeter&) = delete;
   InteriorResizeHandleTargeter& operator=(const InteriorResizeHandleTargeter&) =
       delete;
@@ -24,6 +31,10 @@
                        gfx::Rect* hit_test_rect_mouse,
                        gfx::Rect* hit_test_rect_touch) const override;
   bool ShouldUseExtendedBounds(const aura::Window* target) const override;
+
+ private:
+  // Callback that gets the WindowStateType.
+  const WindowStateTypeCallback window_state_type_cb_;
 };
 
 }  // namespace chromeos
diff --git a/clank b/clank
index fc1c089..96dc252 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit fc1c0897381a14bedb2015ed8d4e0551d2f72088
+Subproject commit 96dc252dcd4740e9bbe8822a8357c5b678cddf01
diff --git a/components/autofill/core/browser/payments/payments_requests/unmask_card_request.cc b/components/autofill/core/browser/payments/payments_requests/unmask_card_request.cc
index 9b6234f8..8886b44 100644
--- a/components/autofill/core/browser/payments/payments_requests/unmask_card_request.cc
+++ b/components/autofill/core/browser/payments/payments_requests/unmask_card_request.cc
@@ -44,87 +44,85 @@
 // Parses the `defined_challenge_option` as a 3ds challenge option, and sets the
 // appropriate fields in `parsed_challenge_option`.
 void ParseAs3dsChallengeOption(
-    const base::Value::Dict* defined_challenge_option,
-    CardUnmaskChallengeOption* parsed_challenge_option) {
-  parsed_challenge_option->type =
+    const base::Value::Dict& defined_challenge_option,
+    CardUnmaskChallengeOption& parsed_challenge_option) {
+  parsed_challenge_option.type =
       CardUnmaskChallengeOptionType::kThreeDomainSecure;
 
   const auto* challenge_id =
-      defined_challenge_option->FindString("challenge_id");
+      defined_challenge_option.FindString("challenge_id");
   if (challenge_id) {
-    parsed_challenge_option->id =
+    parsed_challenge_option.id =
         CardUnmaskChallengeOption::ChallengeOptionId(*challenge_id);
   }
 
-  const auto* url_to_open =
-      defined_challenge_option->FindString("redirect_url");
+  const auto* url_to_open = defined_challenge_option.FindString("redirect_url");
   if (url_to_open) {
-    parsed_challenge_option->url_to_open = GURL(*url_to_open);
+    parsed_challenge_option.url_to_open = GURL(*url_to_open);
   }
 
-  parsed_challenge_option->challenge_info = l10n_util::GetStringUTF16(
+  parsed_challenge_option.challenge_info = l10n_util::GetStringUTF16(
       IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_THREE_DOMAIN_SECURE_CHALLENGE_INFO);
 }
 
 // Parses the `defined_challenge_option` as an  OTP challenge option, and sets
 // the appropriate fields in `parsed_challenge_option`.
 void ParseAsOtpChallengeOption(
-    const base::Value::Dict* defined_challenge_option,
-    CardUnmaskChallengeOption* parsed_challenge_option,
+    const base::Value::Dict& defined_challenge_option,
+    CardUnmaskChallengeOption& parsed_challenge_option,
     CardUnmaskChallengeOptionType otp_challenge_option_type) {
-  parsed_challenge_option->type = otp_challenge_option_type;
+  parsed_challenge_option.type = otp_challenge_option_type;
   const auto* challenge_id =
-      defined_challenge_option->FindString("challenge_id");
+      defined_challenge_option.FindString("challenge_id");
   DCHECK(challenge_id);
-  parsed_challenge_option->id =
+  parsed_challenge_option.id =
       CardUnmaskChallengeOption::ChallengeOptionId(*challenge_id);
 
   const std::string* challenge_info;
   if (otp_challenge_option_type == CardUnmaskChallengeOptionType::kSmsOtp) {
     // For SMS OTP challenge, masked phone number is the `challenge_info` for
     // display.
-    challenge_info =
-        defined_challenge_option->FindString("masked_phone_number");
+    challenge_info = defined_challenge_option.FindString("masked_phone_number");
   } else {
     CHECK_EQ(otp_challenge_option_type,
              CardUnmaskChallengeOptionType::kEmailOtp);
     challenge_info =
-        defined_challenge_option->FindString("masked_email_address");
+        defined_challenge_option.FindString("masked_email_address");
   }
   DCHECK(challenge_info);
-  parsed_challenge_option->challenge_info = base::UTF8ToUTF16(*challenge_info);
+  parsed_challenge_option.challenge_info = base::UTF8ToUTF16(*challenge_info);
 
   // Get the OTP length for this challenge. This will be displayed to the user
   // in the OTP input dialog so that the user knows how many digits the OTP
   // should be.
   std::optional<int> otp_length =
-      defined_challenge_option->FindInt("otp_length");
-  parsed_challenge_option->challenge_input_length =
+      defined_challenge_option.FindInt("otp_length");
+  parsed_challenge_option.challenge_input_length =
       otp_length ? *otp_length : kDefaultOtpLength;
 }
 
 // Parses the `defined_challenge_option` as a CVC challenge option, and sets the
 // appropriate fields in `parsed_challenge_option`.
 void ParseAsCvcChallengeOption(
-    const base::Value::Dict* defined_challenge_option,
-    CardUnmaskChallengeOption* parsed_challenge_option) {
-  parsed_challenge_option->type = CardUnmaskChallengeOptionType::kCvc;
+    const base::Value::Dict& defined_challenge_option,
+    CardUnmaskChallengeOption& parsed_challenge_option) {
+  parsed_challenge_option.type = CardUnmaskChallengeOptionType::kCvc;
 
   // Get the challenge id, which is the unique identifier of this challenge
   // option. The payments server will need this challenge id to know which
   // challenge option was selected.
   const auto* challenge_id =
-      defined_challenge_option->FindString("challenge_id");
+      defined_challenge_option.FindString("challenge_id");
   DCHECK(challenge_id);
-  parsed_challenge_option->id =
+  parsed_challenge_option.id =
       CardUnmaskChallengeOption::ChallengeOptionId(*challenge_id);
 
   // Get the length of the CVC on the card. In most cases this is 3 digits,
   // but it is possible for this to be 4 digits, for example in the case of
   // the Card Identification Number on the front of an American Express card.
   std::optional<int> cvc_length =
-      defined_challenge_option->FindInt("cvc_length");
-  parsed_challenge_option->challenge_input_length =
+      defined_challenge_option.FindInt("cvc_length");
+  parsed_challenge_option.challenge_input_length =
       cvc_length ? *cvc_length : kDefaultCvcLength;
 
   // Get the position of the CVC on the card. In most cases it will be on the
@@ -135,19 +133,19 @@
   // end up displaying the authentication selection dialog.
   std::u16string challenge_info_position_string;
   const auto* cvc_position =
-      defined_challenge_option->FindString("cvc_position");
+      defined_challenge_option.FindString("cvc_position");
   if (cvc_position) {
     if (*cvc_position == "CVC_POSITION_FRONT") {
-      parsed_challenge_option->cvc_position = CvcPosition::kFrontOfCard;
+      parsed_challenge_option.cvc_position = CvcPosition::kFrontOfCard;
       challenge_info_position_string = l10n_util::GetStringUTF16(
           IDS_AUTOFILL_CARD_UNMASK_PROMPT_SECURITY_CODE_POSITION_FRONT_OF_CARD);
     } else if (*cvc_position == "CVC_POSITION_BACK") {
-      parsed_challenge_option->cvc_position = CvcPosition::kBackOfCard;
+      parsed_challenge_option.cvc_position = CvcPosition::kBackOfCard;
       challenge_info_position_string = l10n_util::GetStringUTF16(
           IDS_AUTOFILL_CARD_UNMASK_PROMPT_SECURITY_CODE_POSITION_BACK_OF_CARD);
     } else {
       NOTREACHED_IN_MIGRATION();
-      parsed_challenge_option->cvc_position = CvcPosition::kUnknown;
+      parsed_challenge_option.cvc_position = CvcPosition::kUnknown;
     }
   }
 
@@ -156,9 +154,9 @@
   // in the authentication selection dialog if we have multiple challenge
   // options present.
   if (!challenge_info_position_string.empty()) {
-    parsed_challenge_option->challenge_info = l10n_util::GetStringFUTF16(
+    parsed_challenge_option.challenge_info = l10n_util::GetStringFUTF16(
         IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_CVC_CHALLENGE_INFO,
-        base::NumberToString16(parsed_challenge_option->challenge_input_length),
+        base::NumberToString16(parsed_challenge_option.challenge_input_length),
         challenge_info_position_string);
   }
 }
@@ -173,8 +171,8 @@
   // challenge option, and return it.
   if ((defined_challenge_option =
            challenge_option.FindDict("sms_otp_challenge_option"))) {
-    ParseAsOtpChallengeOption(defined_challenge_option,
-                              &parsed_challenge_option,
+    ParseAsOtpChallengeOption(*defined_challenge_option,
+                              parsed_challenge_option,
                               CardUnmaskChallengeOptionType::kSmsOtp);
   }
   // Check if it's an email OTP challenge option, and if it is, set
@@ -182,8 +180,8 @@
   // challenge option, and return it.
   else if ((defined_challenge_option =
                 challenge_option.FindDict("email_otp_challenge_option"))) {
-    ParseAsOtpChallengeOption(defined_challenge_option,
-                              &parsed_challenge_option,
+    ParseAsOtpChallengeOption(*defined_challenge_option,
+                              parsed_challenge_option,
                               CardUnmaskChallengeOptionType::kEmailOtp);
   }
   // Check if it's a CVC challenge option, and if it is, set
@@ -191,8 +189,8 @@
   // challenge option, and return it.
   else if ((defined_challenge_option =
                 challenge_option.FindDict("cvc_challenge_option"))) {
-    ParseAsCvcChallengeOption(defined_challenge_option,
-                              &parsed_challenge_option);
+    ParseAsCvcChallengeOption(*defined_challenge_option,
+                              parsed_challenge_option);
   }
   // Check if it's a 3ds challenge option, and if it is, set
   // `defined_challenge_option` to the defined challenge option found, parse the
@@ -200,8 +198,8 @@
   else if ((defined_challenge_option =
                 challenge_option.FindDict("redirect_challenge_option")) &&
            IsVcn3dsEnabled()) {
-    ParseAs3dsChallengeOption(defined_challenge_option,
-                              &parsed_challenge_option);
+    ParseAs3dsChallengeOption(*defined_challenge_option,
+                              parsed_challenge_option);
   }
 
   // If it is not a challenge option type that we can parse, return an empty
diff --git a/components/autofill/core/browser/ui/payments/card_unmask_authentication_selection_dialog_controller_impl.cc b/components/autofill/core/browser/ui/payments/card_unmask_authentication_selection_dialog_controller_impl.cc
index 4b71109a..fb82012 100644
--- a/components/autofill/core/browser/ui/payments/card_unmask_authentication_selection_dialog_controller_impl.cc
+++ b/components/autofill/core/browser/ui/payments/card_unmask_authentication_selection_dialog_controller_impl.cc
@@ -28,6 +28,9 @@
           std::move(confirm_unmasking_method_callback)),
       cancel_unmasking_closure_(std::move(cancel_unmasking_closure)) {
   CHECK(!challenge_options_.empty());
+#if BUILDFLAG(IS_IOS)
+  selected_challenge_option_id_ = challenge_options_[0].id;
+#endif  // BUILDFLAG(IS_IOS)
 }
 
 CardUnmaskAuthenticationSelectionDialogControllerImpl::
@@ -227,7 +230,6 @@
   auto selected_challenge_option =
       base::ranges::find(challenge_options_, selected_challenge_option_id_,
                          &CardUnmaskChallengeOption::id);
-
   switch (selected_challenge_option->type) {
     case CardUnmaskChallengeOptionType::kSmsOtp:
     case CardUnmaskChallengeOptionType::kEmailOtp:
diff --git a/components/component_updater/component_installer.cc b/components/component_updater/component_installer.cc
index a8cfc85..f84e1dc1 100644
--- a/components/component_updater/component_installer.cc
+++ b/components/component_updater/component_installer.cc
@@ -87,7 +87,7 @@
 void ComponentInstaller::Register(ComponentUpdateService* cus,
                                   base::OnceClosure callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(cus);
+  CHECK(cus);
 
   std::vector<uint8_t> public_key_hash;
   installer_policy_->GetHash(&public_key_hash);
@@ -183,9 +183,6 @@
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-  DCHECK(!base::PathExists(unpack_path));
-  DCHECK(base::PathExists(local_install_path));
-
 #if BUILDFLAG(IS_APPLE)
   // Since components can be large and can be re-downloaded when needed, they
   // are excluded from backups.
@@ -453,8 +450,8 @@
     const base::Version& max_previous_product_version,
     scoped_refptr<RegistrationInfo> registration_info) {
   VLOG(1) << __func__ << " for " << installer_policy_->GetName();
-  DCHECK(task_runner_);
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  CHECK(task_runner_);
+  CHECK(task_runner_->RunsTasksInCurrentSequence());
 
   // First check for an installation set up alongside Chrome itself.
   base::FilePath root;
@@ -483,8 +480,8 @@
 }
 
 void ComponentInstaller::UninstallOnTaskRunner() {
-  DCHECK(task_runner_);
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  CHECK(task_runner_);
+  CHECK(task_runner_->RunsTasksInCurrentSequence());
 
   const std::optional<base::FilePath> base_dir = GetComponentDirectory();
   if (!base_dir) {
diff --git a/components/component_updater/component_updater_command_line_config_policy.cc b/components/component_updater/component_updater_command_line_config_policy.cc
index 9df7422..8f6a0ef1 100644
--- a/components/component_updater/component_updater_command_line_config_policy.cc
+++ b/components/component_updater/component_updater_command_line_config_policy.cc
@@ -71,7 +71,7 @@
 
 ComponentUpdaterCommandLineConfigPolicy::
     ComponentUpdaterCommandLineConfigPolicy(const base::CommandLine* cmdline) {
-  DCHECK(cmdline);
+  CHECK(cmdline);
   // Parse comma-delimited debug flags.
   std::vector<std::string> switch_values = base::SplitString(
       cmdline->GetSwitchValueASCII(switches::kComponentUpdater), ",",
@@ -93,7 +93,6 @@
       GetSwitchArgument(switch_values, kSwitchUrlSource);
   if (!switch_url_source.empty()) {
     url_source_override_ = GURL(switch_url_source);
-    DCHECK(url_source_override_.is_valid());
   }
 
   const std::string initial_delay =
diff --git a/components/component_updater/component_updater_service.cc b/components/component_updater/component_updater_service.cc
index e3d4fb6..dbc512c 100644
--- a/components/component_updater/component_updater_service.cc
+++ b/components/component_updater/component_updater_service.cc
@@ -331,7 +331,6 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   const auto it = components_.find(id);
   if (it != components_.end()) {
-    DCHECK_EQ(it->first, id);
     if (OnDemandUpdateWithCooldown(id)) {
       ready_callbacks_.insert(std::make_pair(id, std::move(callback)));
       return;
diff --git a/components/content_settings/core/browser/cookie_settings.cc b/components/content_settings/core/browser/cookie_settings.cc
index 84567ef..fc24e6030 100644
--- a/components/content_settings/core/browser/cookie_settings.cc
+++ b/components/content_settings/core/browser/cookie_settings.cc
@@ -192,7 +192,7 @@
 }
 
 bool CookieSettings::IsStoragePartitioningBypassEnabled(
-    const GURL& first_party_url) {
+    const GURL& first_party_url) const {
   SettingInfo info;
   ContentSetting setting = host_content_settings_map_->GetContentSetting(
       GURL(), first_party_url, ContentSettingsType::COOKIES, &info);
@@ -354,12 +354,10 @@
 
 CookieSettings::~CookieSettings() = default;
 
+bool CookieSettings::ShouldBlockThirdPartyCookiesInternal() const {
 #if BUILDFLAG(IS_IOS)
-bool CookieSettings::ShouldBlockThirdPartyCookiesInternal() {
   return false;
-}
 #else
-bool CookieSettings::ShouldBlockThirdPartyCookiesInternal() {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(pref_change_registrar_);
 
@@ -385,11 +383,10 @@
     case CookieControlsMode::kOff:
       return false;
   }
-  return false;
-}
 #endif
+}
 
-bool CookieSettings::MitigationsEnabledFor3pcdInternal() {
+bool CookieSettings::MitigationsEnabledFor3pcdInternal() const {
   if (tracking_protection_settings_ &&
       tracking_protection_settings_->IsTrackingProtection3pcdEnabled()) {
     // Mitigations should be on iff we are not blocking or allowing all 3PC.
diff --git a/components/content_settings/core/browser/cookie_settings.h b/components/content_settings/core/browser/cookie_settings.h
index 62c0edc..04252d0 100644
--- a/components/content_settings/core/browser/cookie_settings.h
+++ b/components/content_settings/core/browser/cookie_settings.h
@@ -157,7 +157,7 @@
   // - Cases like WebUIs, allowlisted internal apps, and extension iframes are
   // usually being exempted from storage partitioning or are allowlisted. Thus,
   // not covered by user bypass at this state of art.
-  bool IsStoragePartitioningBypassEnabled(const GURL& first_party_url);
+  bool IsStoragePartitioningBypassEnabled(const GURL& first_party_url) const;
 
   const ContentSettingsForOneType GetTpcdMetadataGrants() const {
     return tpcd_metadata_manager_ ? tpcd_metadata_manager_->GetGrants()
@@ -239,11 +239,11 @@
  private:
   // Evaluates if third-party cookies are blocked. Should only be called
   // when the preference changes to update the internal state.
-  bool ShouldBlockThirdPartyCookiesInternal();
+  bool ShouldBlockThirdPartyCookiesInternal() const;
 
   // Evaluates whether third party cookies deprecation mitigations should be
   // enabled.
-  bool MitigationsEnabledFor3pcdInternal();
+  bool MitigationsEnabledFor3pcdInternal() const;
 
   void OnCookiePreferencesChanged();
 
diff --git a/components/content_settings/core/common/pref_names.h b/components/content_settings/core/common/pref_names.h
index 2b76605..bd6ed7ba 100644
--- a/components/content_settings/core/common/pref_names.h
+++ b/components/content_settings/core/common/pref_names.h
@@ -17,8 +17,6 @@
 // enabled. This will block third-party cookies similar to
 // kBlockThirdPartyCookies but with a new UI.
 inline constexpr char kCookieControlsMode[] = "profile.cookie_controls_mode";
-inline constexpr char kBlockTruncatedCookies[] =
-    "profile.cookie_block_truncated";
 
 // Version of the pattern format used to define content settings.
 inline constexpr char kContentSettingsVersion[] =
diff --git a/components/cronet/android/cronet_combined_impl_native_proguard_golden.cfg b/components/cronet/android/cronet_combined_impl_native_proguard_golden.cfg
index 4bd3e4aa..72042b2 100644
--- a/components/cronet/android/cronet_combined_impl_native_proguard_golden.cfg
+++ b/components/cronet/android/cronet_combined_impl_native_proguard_golden.cfg
@@ -10,14 +10,6 @@
 # Chromium code. They MUST be scoped appropriately to avoid side effects on app
 # code that we do not own.
 
-# Use assumevalues block instead of assumenosideeffects block because Google3
-# proguard cannot parse assumenosideeffects blocks which overwrite return
-# value. Keep this in shared_with_cronet.flags rather than remove_logging.flags
-# so that it's included in cronet.
--assumevalues class org.chromium.base.Log {
-  static boolean isDebug() return false;
-}
-
 # Keep all CREATOR fields within Parcelable that are kept.
 -keepclassmembers class !cr_allowunused,org.chromium.** implements android.os.Parcelable {
   public static *** CREATOR;
@@ -237,4 +229,4 @@
 # TODO(crbug.com/315973491): Restrict the broad scope of this rule.
 -keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class ** {
   native <methods>;
-}
\ No newline at end of file
+}
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java
index f28673f..c43eccf 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java
@@ -111,7 +111,7 @@
     private final String mInitialUrl;
     private final int mInitialPriority;
     private final String mInitialMethod;
-    private final String mRequestHeaders[];
+    private final String[] mRequestHeaders;
     private final boolean mDelayRequestHeadersUntilFirstFlush;
     private final Collection<Object> mRequestAnnotations;
     private final boolean mTrafficStatsTagSet;
@@ -800,7 +800,8 @@
                             getFinishedReason(),
                             mResponseInfo,
                             mException);
-            mRequestContext.reportRequestFinished(requestFinishedInfo, mInflightDoneCallbackCount);
+            mRequestContext.reportRequestFinished(
+                    requestFinishedInfo, mInflightDoneCallbackCount, null);
         } finally {
             mInflightDoneCallbackCount.decrement();
         }
@@ -969,7 +970,7 @@
     }
 
     private static String[] stringsFromHeaderList(List<Map.Entry<String, String>> headersList) {
-        String headersArray[] = new String[headersList.size() * 2];
+        String[] headersArray = new String[headersList.size() * 2];
         int i = 0;
         for (Map.Entry<String, String> requestHeader : headersList) {
             headersArray[i++] = requestHeader.getKey();
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java
index 205653f11..7c101554 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java
@@ -1063,38 +1063,8 @@
                             mFinishedReason,
                             mResponseInfo,
                             mException);
-            mRequestContext.reportRequestFinished(requestInfo, inflightCallbackCount);
-            if (mRequestFinishedListener != null) {
-                inflightCallbackCount.increment();
-                try {
-                    mRequestFinishedListener
-                            .getExecutor()
-                            .execute(
-                                    new Runnable() {
-                                        @Override
-                                        public void run() {
-                                            try {
-                                                mRequestFinishedListener.onRequestFinished(
-                                                        requestInfo);
-                                            } catch (Exception e) {
-                                                Log.e(
-                                                        CronetUrlRequestContext.LOG_TAG,
-                                                        "Exception thrown from request"
-                                                                + " finishedlistener",
-                                                        e);
-                                            } finally {
-                                                inflightCallbackCount.decrement();
-                                            }
-                                        }
-                                    });
-                } catch (RejectedExecutionException failException) {
-                    Log.e(
-                            CronetUrlRequestContext.LOG_TAG,
-                            "Exception posting task to executor",
-                            failException);
-                    inflightCallbackCount.decrement();
-                }
-            }
+            mRequestContext.reportRequestFinished(
+                    requestInfo, inflightCallbackCount, mRequestFinishedListener);
         } finally {
             inflightCallbackCount.decrement();
         }
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java
index 838b1e9..35d482d1 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java
@@ -813,12 +813,6 @@
         }
     }
 
-    boolean hasRequestFinishedListener() {
-        synchronized (mFinishedListenerLock) {
-            return !mFinishedListenerMap.isEmpty();
-        }
-    }
-
     @Override
     public URLConnection openConnection(URL url) {
         return openConnection(url, Proxy.NO_PROXY);
@@ -974,14 +968,18 @@
     }
 
     void reportRequestFinished(
-            final RequestFinishedInfo requestInfo, RefCountDelegate inflightCallbackCount) {
-        ArrayList<VersionSafeCallbacks.RequestFinishedInfoListener> currentListeners;
+            final RequestFinishedInfo requestInfo,
+            RefCountDelegate inflightCallbackCount,
+            VersionSafeCallbacks.RequestFinishedInfoListener extraRequestFinishedInfoListener) {
+        List<VersionSafeCallbacks.RequestFinishedInfoListener> currentListeners;
         synchronized (mFinishedListenerLock) {
-            if (mFinishedListenerMap.isEmpty()) return;
-            currentListeners =
-                    new ArrayList<VersionSafeCallbacks.RequestFinishedInfoListener>(
-                            mFinishedListenerMap.values());
+            if (mFinishedListenerMap.isEmpty() && extraRequestFinishedInfoListener == null) return;
+            currentListeners = new ArrayList<>(mFinishedListenerMap.values());
         }
+        if (extraRequestFinishedInfoListener != null) {
+            currentListeners.add(extraRequestFinishedInfoListener);
+        }
+
         for (final VersionSafeCallbacks.RequestFinishedInfoListener listener : currentListeners) {
             Runnable task =
                     new Runnable() {
diff --git a/components/embedder_support/android/util/input_stream.cc b/components/embedder_support/android/util/input_stream.cc
index 8cdb9521..54623c4 100644
--- a/components/embedder_support/android/util/input_stream.cc
+++ b/components/embedder_support/android/util/input_stream.cc
@@ -5,6 +5,8 @@
 #include "components/embedder_support/android/util/input_stream.h"
 
 #include "base/android/jni_android.h"
+#include "base/feature_list.h"
+#include "base/metrics/field_trial_params.h"
 // Disable "Warnings treated as errors" for input_stream_jni as it's a Java
 // system class and we have to generate C++ hooks for all methods in the class
 // even if they're unused.
@@ -26,8 +28,21 @@
 const int kExceptionThrownStatusCode = -2;
 }  // namespace
 
-// Maximum number of bytes to be read in a single read.
-const int InputStream::kBufferSize = 4096;
+// Experiment to control the size of the intermediate buffer used to copy from
+// Java's InputStream into C++'s net::IOBuffer.
+BASE_FEATURE(kEnableCustomInputStreamBufferSize,
+             "EnableCustomInputStreamBufferSize",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+// Effectively the maximum number of bytes that will be copied during a JNI call
+// to Java_InputStreamUtil_read.
+const base::FeatureParam<int> kBufferSize{&kEnableCustomInputStreamBufferSize,
+                                          "BufferSize", 4096};
+
+// static
+int InputStream::GetIntermediateBufferSize() {
+  return kBufferSize.Get();
+}
 
 // TODO: Use unsafe version for all Java_InputStream methods in this file
 // once BUG 157880 is fixed and implement graceful exception handling.
@@ -69,7 +84,7 @@
   if (!buffer_.obj()) {
     // Allocate transfer buffer.
     base::android::ScopedJavaLocalRef<jbyteArray> temp(
-        env, env->NewByteArray(kBufferSize));
+        env, env->NewByteArray(GetIntermediateBufferSize()));
     buffer_.Reset(temp);
     if (ClearException(env))
       return false;
@@ -80,7 +95,8 @@
   *bytes_read = 0;
 
   while (remaining_length > 0) {
-    const int max_transfer_length = std::min(remaining_length, kBufferSize);
+    const int max_transfer_length =
+        std::min(remaining_length, GetIntermediateBufferSize());
     const int transfer_length = Java_InputStreamUtil_read(
         env, jobject_, buffer_, 0, max_transfer_length);
     if (transfer_length == kExceptionThrownStatusCode)
diff --git a/components/embedder_support/android/util/input_stream.h b/components/embedder_support/android/util/input_stream.h
index 2a9a097..48df69e1 100644
--- a/components/embedder_support/android/util/input_stream.h
+++ b/components/embedder_support/android/util/input_stream.h
@@ -9,6 +9,7 @@
 
 #include "base/android/scoped_java_ref.h"
 #include "base/compiler_specific.h"
+#include "base/feature_list.h"
 
 namespace net {
 class IOBuffer;
@@ -16,14 +17,17 @@
 
 namespace embedder_support {
 
+BASE_DECLARE_FEATURE(kEnableCustomInputStreamBufferSize);
+
 // Abstract wrapper used to access the InputStream Java class.
 // This class is safe to pass around between threads (the destructor,
 // constructor and methods can be called on different threads) but calling
 // methods concurrently might have undefined results.
 class InputStream {
  public:
-  // Maximum size of |buffer_|.
-  static const int kBufferSize;
+  // Returns the size to be used for the intermediate buffer to copy from Java's
+  // InputStream into C++'s net::IOBuffer.
+  static int GetIntermediateBufferSize();
 
   // |stream| should be an instance of the InputStream Java class.
   // |stream| can't be null.
diff --git a/components/embedder_support/android/util/input_stream_unittest.cc b/components/embedder_support/android/util/input_stream_unittest.cc
index a01d9bf..67a9917 100644
--- a/components/embedder_support/android/util/input_stream_unittest.cc
+++ b/components/embedder_support/android/util/input_stream_unittest.cc
@@ -2,13 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "components/embedder_support/android/util/input_stream.h"
+
 #include <memory>
 
 #include "base/android/jni_android.h"
 #include "base/android/scoped_java_ref.h"
 #include "base/memory/raw_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "components/embedder_support/android/native_j_unittests_jni_headers/InputStreamUnittest_jni.h"
-#include "components/embedder_support/android/util/input_stream.h"
 #include "net/base/io_buffer.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_byte_range.h"
@@ -88,7 +91,7 @@
 }
 
 TEST_F(InputStreamTest, TryReadMoreThanBuffer) {
-  const int buffer_size = 3 * InputStream::kBufferSize;
+  const int buffer_size = 3 * InputStream::GetIntermediateBufferSize();
   int bytes_read = 0;
   DoReadCountedStreamTest(buffer_size, buffer_size * 2, &bytes_read);
   EXPECT_EQ(buffer_size, bytes_read);
@@ -106,19 +109,32 @@
 }
 
 TEST_F(InputStreamTest, ReadLargeStreamPartial) {
-  const int bytes_requested = 3 * InputStream::kBufferSize;
+  const int bytes_requested = 3 * InputStream::GetIntermediateBufferSize();
   int bytes_read = 0;
   DoReadCountedStreamTest(bytes_requested + 32, bytes_requested, &bytes_read);
   EXPECT_EQ(bytes_requested, bytes_read);
 }
 
 TEST_F(InputStreamTest, ReadLargeStreamCompletely) {
-  const int bytes_requested = 3 * InputStream::kBufferSize;
+  const int bytes_requested = 3 * InputStream::GetIntermediateBufferSize();
   int bytes_read = 0;
   DoReadCountedStreamTest(bytes_requested, bytes_requested, &bytes_read);
   EXPECT_EQ(bytes_requested, bytes_read);
 }
 
+TEST_F(InputStreamTest, CustomInputStreamBufferSize) {
+  constexpr int custom_buffer_size = 1024;
+  EXPECT_NE(InputStream::GetIntermediateBufferSize(), custom_buffer_size);
+
+  base::test::ScopedFeatureList feature_list;
+  base::FieldTrialParams params;
+  params["BufferSize"] = base::NumberToString(custom_buffer_size);
+
+  feature_list.InitAndEnableFeatureWithParameters(
+      embedder_support::kEnableCustomInputStreamBufferSize, params);
+  EXPECT_EQ(InputStream::GetIntermediateBufferSize(), custom_buffer_size);
+}
+
 TEST_F(InputStreamTest, DoesNotCrashWhenExceptionThrown) {
   ScopedJavaLocalRef<jobject> throw_jstream =
       Java_InputStreamUnittest_getThrowingStream(env_);
diff --git a/components/enterprise/data_controls/BUILD.gn b/components/enterprise/data_controls/BUILD.gn
index 0d472135..c4b5096 100644
--- a/components/enterprise/data_controls/BUILD.gn
+++ b/components/enterprise/data_controls/BUILD.gn
@@ -70,6 +70,7 @@
   ]
   public_deps = [
     ":data_controls",
+    ":features",
     "//base",
     "//components/policy/core/common:common_constants",
     "//components/prefs",
diff --git a/components/enterprise/data_controls/features.cc b/components/enterprise/data_controls/features.cc
index 2eda736..08782ee6 100644
--- a/components/enterprise/data_controls/features.cc
+++ b/components/enterprise/data_controls/features.cc
@@ -10,4 +10,8 @@
              "EnableDesktopDataControls",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kEnableScreenshotProtection,
+             "EnableScreenshotProtection",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace data_controls
diff --git a/components/enterprise/data_controls/features.h b/components/enterprise/data_controls/features.h
index c12e6b5c..bc409df 100644
--- a/components/enterprise/data_controls/features.h
+++ b/components/enterprise/data_controls/features.h
@@ -12,8 +12,18 @@
 // Controls enabling Data Controls for all desktop browser platforms (Windows,
 // Mac, Linux, CrOS). Policies controlling cross-platform Data Controls will be
 // ignored if this feature is disabled.
+//
+// Use `kEnableScreenshotProtection` to gate the implementation of screenshot
+// protection rules instead of this feature.
 BASE_DECLARE_FEATURE(kEnableDesktopDataControls);
 
+// Controls enabling screenshot blocking Data Controls rules for supported
+// desktop browser platforms (Windows, Mac).
+//
+// Use `kEnableDesktopDataControls` to gate the implementation ofother rule
+// types.
+BASE_DECLARE_FEATURE(kEnableScreenshotProtection);
+
 }  // namespace data_controls
 
 #endif  // COMPONENTS_ENTERPRISE_DATA_CONTROLS_FEATURES_H_
diff --git a/components/enterprise/data_controls/rule.cc b/components/enterprise/data_controls/rule.cc
index a07583a..e588b76 100644
--- a/components/enterprise/data_controls/rule.cc
+++ b/components/enterprise/data_controls/rule.cc
@@ -8,10 +8,12 @@
 #include <vector>
 
 #include "base/containers/fixed_flat_map.h"
+#include "base/feature_list.h"
 #include "base/logging.h"
 #include "base/strings/string_util.h"
 #include "components/enterprise/data_controls/and_condition.h"
 #include "components/enterprise/data_controls/attributes_condition.h"
+#include "components/enterprise/data_controls/features.h"
 #include "components/enterprise/data_controls/not_condition.h"
 #include "components/enterprise/data_controls/or_condition.h"
 #include "components/policy/core/browser/policy_error_map.h"
@@ -96,6 +98,15 @@
   return new_error_path;
 }
 
+// Helper to check if a restriction is allowed to be applied to a rule given
+// the currently enabled features.
+bool IgnoreRestriction(Rule::Restriction restriction) {
+  if (restriction == Rule::Restriction::kScreenshot) {
+    return !base::FeatureList::IsEnabled(kEnableScreenshotProtection);
+  }
+  return !base::FeatureList::IsEnabled(kEnableDesktopDataControls);
+}
+
 }  // namespace
 
 Rule::Rule(Rule&& other) = default;
@@ -128,6 +139,15 @@
   }
 
   auto restrictions = GetRestrictions(value);
+
+  // To avoid create a `Rule` object with restrictions for disabled features,
+  // `restrictions` is first filtered. This is done here instead of in
+  // `GetRestrictions()` so that it can still be used by policy handler code to
+  // parse the policy correctly.
+  base::EraseIf(restrictions, [](const auto& entry) {
+    return IgnoreRestriction(entry.first);
+  });
+
   if (restrictions.empty()) {
     return std::nullopt;
   }
diff --git a/components/enterprise/data_controls/rule.h b/components/enterprise/data_controls/rule.h
index eeec092..c2ed7ab 100644
--- a/components/enterprise/data_controls/rule.h
+++ b/components/enterprise/data_controls/rule.h
@@ -145,7 +145,7 @@
   // }
   // For compatibility, unrecognized values are ignored and valid values are
   // still included in the output.
-  static base::flat_map<Rule::Restriction, Rule::Level> GetRestrictions(
+  static base::flat_map<Restriction, Level> GetRestrictions(
       const base::Value::Dict& value);
 
   // Helper used to recursively validate a rule. This should only be called by
@@ -153,7 +153,7 @@
   static bool ValidateRuleSubValues(
       const char* policy_name,
       const base::Value::Dict& value,
-      const base::flat_map<Rule::Restriction, Rule::Level>& restrictions,
+      const base::flat_map<Restriction, Level>& restrictions,
       policy::PolicyErrorPath error_path,
       policy::PolicyErrorMap* errors);
 
@@ -172,7 +172,7 @@
   static bool AddUnsupportedAttributeErrors(
       const std::vector<std::string_view>& oneof_conditions,
       const std::vector<std::string_view>& anyof_conditions,
-      base::flat_map<Rule::Restriction, Rule::Level> restrictions,
+      base::flat_map<Restriction, Level> restrictions,
       const char* policy_name,
       policy::PolicyErrorPath error_path,
       policy::PolicyErrorMap* errors);
@@ -182,7 +182,7 @@
   // if at least one error was added.
   static bool AddUnsupportedRestrictionErrors(
       const char* policy_name,
-      const base::flat_map<Rule::Restriction, Rule::Level>& restrictions,
+      const base::flat_map<Restriction, Level>& restrictions,
       policy::PolicyErrorPath error_path,
       policy::PolicyErrorMap* errors);
 
diff --git a/components/enterprise/data_controls/rule_unittest.cc b/components/enterprise/data_controls/rule_unittest.cc
index bc21282..f25e3a3 100644
--- a/components/enterprise/data_controls/rule_unittest.cc
+++ b/components/enterprise/data_controls/rule_unittest.cc
@@ -7,10 +7,13 @@
 #include <tuple>
 #include <vector>
 
+#include "base/feature_list.h"
 #include "base/json/json_reader.h"
 #include "base/logging.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/values.h"
+#include "components/enterprise/data_controls/features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace data_controls {
@@ -23,6 +26,48 @@
   return Rule::Create(*dict);
 }
 
+class DataControlsRuleTest : public testing::Test {
+ public:
+  explicit DataControlsRuleTest(bool desktop_feature_enabled = true,
+                                bool screenshot_feature_enabled = true) {
+    std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
+
+    if (desktop_feature_enabled) {
+      enabled_features.push_back(kEnableDesktopDataControls);
+    } else {
+      disabled_features.push_back(kEnableDesktopDataControls);
+    }
+
+    if (screenshot_feature_enabled) {
+      enabled_features.push_back(kEnableScreenshotProtection);
+    } else {
+      disabled_features.push_back(kEnableScreenshotProtection);
+    }
+
+    scoped_features_.InitWithFeatures(enabled_features, disabled_features);
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_features_;
+};
+
+class DataControlsFeaturesRuleTest
+    : public DataControlsRuleTest,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  DataControlsFeaturesRuleTest()
+      : DataControlsRuleTest(desktop_feature_enabled(),
+                             screenshot_feature_enabled()) {}
+
+  bool desktop_feature_enabled() const { return std::get<0>(GetParam()); }
+  bool screenshot_feature_enabled() const { return std::get<1>(GetParam()); }
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         DataControlsFeaturesRuleTest,
+                         testing::Combine(testing::Bool(), testing::Bool()));
+
 struct AndOrNotTestCase {
   const char* conditions;
   ActionContext context;
@@ -33,7 +78,8 @@
 // attribute. This is parametrized with conditions and a corresponding context
 // to trigger them.
 class DataControlsRuleNotTest
-    : public testing::TestWithParam<AndOrNotTestCase> {
+    : public DataControlsRuleTest,
+      public testing::WithParamInterface<AndOrNotTestCase> {
  public:
   std::string normal_rule_string() {
     return base::StringPrintf(R"(
@@ -68,7 +114,8 @@
 // inserted into an "and" attribute. This is parametrized with conditions and a
 // corresponding context to trigger them.
 class DataControlsRuleAndTest
-    : public testing::TestWithParam<AndOrNotTestCase> {
+    : public DataControlsRuleTest,
+      public testing::WithParamInterface<AndOrNotTestCase> {
  public:
   std::string rule_string() {
     return base::StringPrintf(R"(
@@ -89,7 +136,9 @@
 // Test to validate that a valid set of conditions in a rule will trigger when
 // inserted into an "or" attribute. This is parametrized with conditions and a
 // corresponding context to trigger them.
-class DataControlsRuleOrTest : public testing::TestWithParam<AndOrNotTestCase> {
+class DataControlsRuleOrTest
+    : public DataControlsRuleTest,
+      public testing::WithParamInterface<AndOrNotTestCase> {
  public:
   std::string rule_string() {
     return base::StringPrintf(R"(
@@ -229,7 +278,7 @@
 
 }  // namespace
 
-TEST(DataControlsRuleTest, InvalidValues) {
+TEST_F(DataControlsRuleTest, InvalidValues) {
   ASSERT_FALSE(Rule::Create(base::Value(1)));
   ASSERT_FALSE(Rule::Create(base::Value(-1)));
   ASSERT_FALSE(Rule::Create(base::Value(true)));
@@ -244,7 +293,7 @@
   ASSERT_FALSE(Rule::Create(base::Value(std::vector<char>({1, 2, 3, 4}))));
 }
 
-TEST(DataControlsRuleTest, InvalidConditions) {
+TEST_F(DataControlsRuleTest, InvalidConditions) {
   // First parameter should be "sources", second one should be "destinations".
   constexpr char kTemplate[] = R"({
     "name": "Block pastes",
@@ -298,7 +347,7 @@
       kTemplate, "", R"("or": {"sources": {"urls": ["or.is.not.a.dict"]}},)")));
 }
 
-TEST(DataControlsRuleTest, ValidSourcesInvalidDestinationsConditions) {
+TEST_F(DataControlsRuleTest, ValidSourcesInvalidDestinationsConditions) {
   // Rules with a valid sources but invalid destinations should be created for
   // forward compatibility.
   constexpr char kTemplate[] = R"({
@@ -329,7 +378,7 @@
 #endif  // BUILDFLAG(IS_CHROMEOS)
 }
 
-TEST(DataControlsRuleTest, InvalidSourcesValidDestinationsConditions) {
+TEST_F(DataControlsRuleTest, InvalidSourcesValidDestinationsConditions) {
   // Rules with a valid destinations but valid destinations should be created
   // for forward compatibility.
   constexpr char kTemplate[] = R"({
@@ -352,7 +401,7 @@
       kTemplate, R"("sources": {"urls": ["not_a_real:pattern"]},)")));
 }
 
-TEST(DataControlsRuleTest, NoRestrictions) {
+TEST_F(DataControlsRuleTest, NoRestrictions) {
   ASSERT_FALSE(MakeRule(R"({
     "name": "Block pastes",
     "rule_id": "1234",
@@ -361,7 +410,7 @@
   })"));
 }
 
-TEST(DataControlsRuleTest, InvalidRestrictions) {
+TEST_F(DataControlsRuleTest, InvalidRestrictions) {
   constexpr char kTemplate[] = R"({
     "name": "Block pastes",
     "rule_id": "1234",
@@ -377,7 +426,7 @@
       MakeRule(base::StringPrintf(kTemplate, R"(["not_a_real_restriction"])")));
 }
 
-TEST(DataControlsRuleTest, Restrictions) {
+TEST_F(DataControlsRuleTest, Restrictions) {
   auto rule = MakeRule(R"({
     "name": "Block pastes",
     "rule_id": "1234",
@@ -407,7 +456,7 @@
             Rule::Level::kNotSet);
 }
 
-TEST(DataControlsRuleTest, Accessors) {
+TEST_F(DataControlsRuleTest, Accessors) {
   auto rule = MakeRule(R"({
     "name": "Block pastes",
     "rule_id": "1234",
@@ -424,7 +473,7 @@
   ASSERT_EQ(rule->description(), "A test rule to block pastes");
 }
 
-TEST(DataControlsRuleTest, SourceUrls) {
+TEST_F(DataControlsRuleTest, SourceUrls) {
   auto rule = MakeRule(R"({
     "name": "Block pastes",
     "rule_id": "1234",
@@ -444,7 +493,7 @@
             Rule::Level::kNotSet);
 }
 
-TEST(DataControlsRuleTest, DestinationUrls) {
+TEST_F(DataControlsRuleTest, DestinationUrls) {
   auto rule = MakeRule(R"({
     "name": "Block pastes",
     "rule_id": "1234",
@@ -466,7 +515,7 @@
       Rule::Level::kNotSet);
 }
 
-TEST(DataControlsRuleTest, SourceAndDestinationUrls) {
+TEST_F(DataControlsRuleTest, SourceAndDestinationUrls) {
   auto rule = MakeRule(R"({
     "name": "Block pastes",
     "rule_id": "1234",
@@ -510,7 +559,7 @@
 }
 
 #if BUILDFLAG(IS_CHROMEOS)
-TEST(DataControlsRuleTest, DestinationComponent) {
+TEST_F(DataControlsRuleTest, DestinationComponent) {
   // A "FOO" component is included to validate that compatibility with future
   // components works and doesn't interfere with the rest of the rule.
   auto rule = MakeRule(R"({
@@ -548,6 +597,55 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
+TEST_P(DataControlsFeaturesRuleTest, ScreenshotRules) {
+  auto rule = MakeRule(R"({
+    "name": "Block screenshots",
+    "rule_id": "1234",
+    "description": "A test rule to block screenshots",
+    "sources": { "urls": ["*"] },
+    "restrictions": [
+      { "class": "SCREENSHOT", "level": "BLOCK" }
+    ]
+  })");
+  if (screenshot_feature_enabled()) {
+    ASSERT_TRUE(rule);
+    ASSERT_EQ(rule->GetLevel(Rule::Restriction::kScreenshot,
+                             {.source = {.url = GURL("https://google.com")}}),
+              Rule::Level::kBlock);
+  } else {
+    ASSERT_FALSE(rule);
+  }
+}
+
+TEST_P(DataControlsFeaturesRuleTest, NonScreenshotRules) {
+  auto rule = MakeRule(R"({
+    "name": "Block stuff",
+    "rule_id": "1234",
+    "description": "A test rule to block some non-screenshot actions",
+    "destinations": { "urls": ["*"] },
+    "restrictions": [
+      { "class": "CLIPBOARD", "level": "BLOCK" },
+      { "class": "PRINTING", "level": "ALLOW" },
+      { "class": "PRIVACY_SCREEN", "level": "REPORT" }
+    ]
+  })");
+  if (desktop_feature_enabled()) {
+    ASSERT_TRUE(rule);
+    ActionContext context = {
+        .destination = {.url = GURL("https://google.com")}};
+    ASSERT_EQ(rule->GetLevel(Rule::Restriction::kClipboard, context),
+              Rule::Level::kBlock);
+    ASSERT_EQ(rule->GetLevel(Rule::Restriction::kPrinting, context),
+              Rule::Level::kAllow);
+    ASSERT_EQ(rule->GetLevel(Rule::Restriction::kPrivacyScreen, context),
+              Rule::Level::kReport);
+    ASSERT_EQ(rule->GetLevel(Rule::Restriction::kScreenshot, context),
+              Rule::Level::kNotSet);
+  } else {
+    ASSERT_FALSE(rule);
+  }
+}
+
 TEST_P(DataControlsRuleNotTest, TriggeringContext) {
   auto normal_rule = MakeRule(normal_rule_string());
   auto negative_rule = MakeRule(negative_rule_string());
diff --git a/components/enterprise/data_controls/rules_service.cc b/components/enterprise/data_controls/rules_service.cc
index 0d8b008e..4bdf650 100644
--- a/components/enterprise/data_controls/rules_service.cc
+++ b/components/enterprise/data_controls/rules_service.cc
@@ -10,7 +10,8 @@
 namespace data_controls {
 
 RulesService::RulesService(PrefService* pref_service) {
-  if (base::FeatureList::IsEnabled(kEnableDesktopDataControls)) {
+  if (base::FeatureList::IsEnabled(kEnableDesktopDataControls) ||
+      base::FeatureList::IsEnabled(kEnableScreenshotProtection)) {
     pref_registrar_.Init(pref_service);
     pref_registrar_.Add(
         kDataControlsRulesPref,
@@ -24,7 +25,8 @@
 
 Verdict RulesService::GetVerdict(Rule::Restriction restriction,
                                  const ActionContext& context) const {
-  if (!base::FeatureList::IsEnabled(kEnableDesktopDataControls)) {
+  if (!base::FeatureList::IsEnabled(kEnableDesktopDataControls) &&
+      !base::FeatureList::IsEnabled(kEnableScreenshotProtection)) {
     return Verdict::NotSet();
   }
 
@@ -56,10 +58,6 @@
 
 void RulesService::OnDataControlsRulesUpdate() {
   DCHECK(pref_registrar_.prefs());
-  if (!base::FeatureList::IsEnabled(kEnableDesktopDataControls)) {
-    return;
-  }
-
   rules_.clear();
 
   const base::Value::List& rules_list =
diff --git a/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request.cc b/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request.cc
index 7805791..998c2f46 100644
--- a/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request.cc
+++ b/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request.cc
@@ -124,10 +124,12 @@
 }
 
 bool FacilitatedPaymentsInitiatePaymentRequest::IsResponseComplete() {
-  return false;
+  return !response_details_->action_token_.empty();
 }
 
 void FacilitatedPaymentsInitiatePaymentRequest::RespondToDelegate(
-    autofill::AutofillClient::PaymentsRpcResult result) {}
+    autofill::AutofillClient::PaymentsRpcResult result) {
+  std::move(response_callback_).Run(result, std::move(response_details_));
+}
 
 }  // namespace payments::facilitated
diff --git a/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request_unittest.cc b/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request_unittest.cc
index dd09f0f574..af35d3ce 100644
--- a/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request_unittest.cc
+++ b/components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request_unittest.cc
@@ -98,6 +98,9 @@
 
   std::vector<uint8_t> expected_action_token = {'t', 'o', 'k', 'e', 'n'};
   EXPECT_EQ(expected_action_token, request->response_details_->action_token_);
+
+  // Verify that the response is considered complete.
+  EXPECT_TRUE(request->IsResponseComplete());
 }
 
 TEST_F(FacilitatedPaymentsInitiatePaymentRequestTest,
@@ -112,6 +115,9 @@
 
   EXPECT_EQ("Something went wrong!",
             request->response_details_->error_message_.value());
+
+  // Verify that the response is considered incomplete.
+  EXPECT_FALSE(request->IsResponseComplete());
 }
 
 }  // namespace payments::facilitated
diff --git a/components/feature_engagement/public/feature_configurations.cc b/components/feature_engagement/public/feature_configurations.cc
index 0d1b8342..ede7bae 100644
--- a/components/feature_engagement/public/feature_configurations.cc
+++ b/components/feature_engagement/public/feature_configurations.cc
@@ -289,6 +289,22 @@
     return config;
   }
 
+  if (kIPHExplicitBrowserSigninPreferenceRememberedFeature.name ==
+      feature->name) {
+    std::optional<FeatureConfig> config = FeatureConfig();
+    config->valid = true;
+    config->availability = Comparator(ANY, 0);
+    config->session_rate = Comparator(ANY, 0);
+    config->session_rate_impact.type = SessionRateImpact::Type::ALL;
+    config->trigger = EventConfig(
+        "iph_explicit_browser_signin_preference_remembered_triggered",
+        Comparator(ANY, 0), 0, 0);
+    config->used =
+        EventConfig("iph_explicit_browser_signin_preference_remembered_used",
+                    Comparator(ANY, 0), 0, 0);
+    return config;
+  }
+
   if (kIPHTrackingProtectionOffboardingFeature.name == feature->name) {
     std::optional<FeatureConfig> config = FeatureConfig();
     config->valid = true;
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index 5b4a9cc..c298a4b3 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -61,6 +61,9 @@
 BASE_FEATURE(kIPHExperimentalAIPromoFeature,
              "IPH_ExperimentalAIPromo",
              base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kIPHExplicitBrowserSigninPreferenceRememberedFeature,
+             "IPH_ExplicitBrowserSigninPreferenceRemembered",
+             base::FEATURE_ENABLED_BY_DEFAULT);
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 BASE_FEATURE(kIPHExtensionsMenuFeature,
              "IPH_ExtensionsMenu",
diff --git a/components/feature_engagement/public/feature_constants.h b/components/feature_engagement/public/feature_constants.h
index 4d12e351..b766ee9a 100644
--- a/components/feature_engagement/public/feature_constants.h
+++ b/components/feature_engagement/public/feature_constants.h
@@ -35,6 +35,7 @@
 BASE_DECLARE_FEATURE(kIPHDiscardRingFeature);
 BASE_DECLARE_FEATURE(kIPHDownloadEsbPromoFeature);
 BASE_DECLARE_FEATURE(kIPHExperimentalAIPromoFeature);
+BASE_DECLARE_FEATURE(kIPHExplicitBrowserSigninPreferenceRememberedFeature);
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 BASE_DECLARE_FEATURE(kIPHExtensionsMenuFeature);
 BASE_DECLARE_FEATURE(kIPHExtensionsRequestAccessButtonFeature);
diff --git a/components/feature_engagement/public/feature_list.cc b/components/feature_engagement/public/feature_list.cc
index d6ff1891..e33847b 100644
--- a/components/feature_engagement/public/feature_list.cc
+++ b/components/feature_engagement/public/feature_list.cc
@@ -162,6 +162,7 @@
     &kIPHDiscardRingFeature,
     &kIPHDownloadEsbPromoFeature,
     &kIPHExperimentalAIPromoFeature,
+    &kIPHExplicitBrowserSigninPreferenceRememberedFeature,
 #if BUILDFLAG(ENABLE_EXTENSIONS)
     &kIPHExtensionsMenuFeature,
     &kIPHExtensionsRequestAccessButtonFeature,
diff --git a/components/feature_engagement/public/feature_list.h b/components/feature_engagement/public/feature_list.h
index dc8c1be..e172dfe 100644
--- a/components/feature_engagement/public/feature_list.h
+++ b/components/feature_engagement/public/feature_list.h
@@ -288,6 +288,8 @@
                        "IPH_DownloadToolbarButton");
 DEFINE_VARIATION_PARAM(kIPHExperimentalAIPromoFeature,
                        "IPH_ExperimentalAIPromo");
+DEFINE_VARIATION_PARAM(kIPHExplicitBrowserSigninPreferenceRememberedFeature,
+                       "IPH_ExplicitBrowserSigninPreferenceRemembered");
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 DEFINE_VARIATION_PARAM(kIPHExtensionsMenuFeature, "IPH_ExtensionsMenu");
 DEFINE_VARIATION_PARAM(kIPHExtensionsRequestAccessButtonFeature,
@@ -618,6 +620,7 @@
         VARIATION_ENTRY(kIPHDiscardRingFeature),
         VARIATION_ENTRY(kIPHDownloadToolbarButtonFeature),
         VARIATION_ENTRY(kIPHExperimentalAIPromoFeature),
+        VARIATION_ENTRY(kIPHExplicitBrowserSigninPreferenceRememberedFeature),
 #if BUILDFLAG(ENABLE_EXTENSIONS)
         VARIATION_ENTRY(kIPHExtensionsMenuFeature),
         VARIATION_ENTRY(kIPHExtensionsRequestAccessButtonFeature),
diff --git a/components/heap_profiling/in_process/BUILD.gn b/components/heap_profiling/in_process/BUILD.gn
index c68c5712..0018ed9 100644
--- a/components/heap_profiling/in_process/BUILD.gn
+++ b/components/heap_profiling/in_process/BUILD.gn
@@ -10,6 +10,14 @@
   deps = [ "//mojo/public/mojom/base" ]
 }
 
+mojom("test_mojom") {
+  sources = [ "mojom/test_connector.mojom" ]
+  deps = [
+    ":mojom",
+    "//components/metrics/public/mojom:call_stack_mojo_bindings",
+  ]
+}
+
 source_set("in_process") {
   # HeapProfilerController's dependencies are not compiled on iOS unless
   # use_allocator_shim is true.
@@ -53,12 +61,14 @@
     deps = [
       ":in_process",
       ":mojom",
+      ":test_mojom",
       "//base/test:test_support",
       "//components/metrics",
       "//components/metrics:child_call_stack_profile_builder",
       "//components/metrics/public/mojom:call_stack_mojo_bindings",
       "//components/variations",
       "//components/version_info",
+      "//mojo/core/test:test_support",
       "//mojo/public/cpp/bindings",
     ]
   }
diff --git a/components/heap_profiling/in_process/DEPS b/components/heap_profiling/in_process/DEPS
index bb01966..62a658e 100644
--- a/components/heap_profiling/in_process/DEPS
+++ b/components/heap_profiling/in_process/DEPS
@@ -10,7 +10,8 @@
 specific_include_rules = {
   "heap_profiler_controller_unittest\.cc": [
     "+components/metrics/public/mojom/call_stack_profile_collector.mojom.h",
-    "+third_party/metrics_proto/execution_context.pb.h",
-    "+third_party/metrics_proto/sampled_profile.pb.h",
+    "+mojo/core/embedder",
+    "+mojo/public/cpp",
+    "+third_party/metrics_proto",
   ],
 }
diff --git a/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc b/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc
index 2f3478453..66726b4 100644
--- a/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc
+++ b/components/heap_profiling/in_process/heap_profiler_controller_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/heap_profiling/in_process/heap_profiler_controller.h"
 
 #include <atomic>
+#include <map>
 #include <memory>
 #include <string>
 #include <utility>
@@ -23,11 +24,17 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/notreached.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
 #include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
 #include "base/sampling_heap_profiler/sampling_heap_profiler.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/bind_post_task.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/multiprocess_test.h"
 #include "base/test/scoped_command_line.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
@@ -39,16 +46,25 @@
 #include "components/heap_profiling/in_process/child_process_snapshot_controller.h"
 #include "components/heap_profiling/in_process/heap_profiler_parameters.h"
 #include "components/heap_profiling/in_process/mojom/snapshot_controller.mojom.h"
+#include "components/heap_profiling/in_process/mojom/test_connector.mojom.h"
 #include "components/heap_profiling/in_process/switches.h"
 #include "components/metrics/call_stacks/call_stack_profile_builder.h"
 #include "components/metrics/call_stacks/call_stack_profile_params.h"
 #include "components/metrics/public/mojom/call_stack_profile_collector.mojom.h"
 #include "components/version_info/channel.h"
+#include "mojo/core/embedder/scoped_ipc_support.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
-#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+#include "mojo/public/cpp/bindings/unique_receiver_set.h"
+#include "mojo/public/cpp/platform/platform_channel.h"
+#include "mojo/public/cpp/system/invitation.h"
+#include "mojo/public/cpp/system/message_pipe.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+#include "third_party/metrics_proto/call_stack_profile.pb.h"
 #include "third_party/metrics_proto/execution_context.pb.h"
 #include "third_party/metrics_proto/sampled_profile.pb.h"
 
@@ -56,11 +72,19 @@
 
 namespace {
 
+#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
+#define ENABLE_MULTIPROCESS_TESTS 0
+#else
+#define ENABLE_MULTIPROCESS_TESTS 1
+#endif
+
 using FeatureRef = base::test::FeatureRef;
 using FeatureRefAndParams = base::test::FeatureRefAndParams;
 using ProcessType = metrics::CallStackProfileParams::Process;
 using ProcessTypeSet =
     base::EnumSet<ProcessType, ProcessType::kUnknown, ProcessType::kMax>;
+using ProfileCollectorCallback =
+    base::RepeatingCallback<void(base::TimeTicks, metrics::SampledProfile)>;
 using base::allocator::dispatcher::AllocationNotificationData;
 using base::allocator::dispatcher::AllocationSubsystem;
 using base::allocator::dispatcher::FreeNotificationData;
@@ -69,12 +93,16 @@
 using ScopedSuppressRandomnessForTesting =
     base::PoissonAllocationSampler::ScopedSuppressRandomnessForTesting;
 
+using ::testing::AllOf;
+using ::testing::Conditional;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::Property;
+using ::testing::UnorderedElementsAre;
+
 constexpr size_t kSamplingRate = 1024;
 constexpr size_t kAllocationSize = 42 * kSamplingRate;
 
-using ProfileCollectorCallback =
-    base::RepeatingCallback<void(base::TimeTicks, metrics::SampledProfile)>;
-
 // A fake CallStackProfileCollector that deserializes profiles it receives from
 // a fake child process, and passes them to the same callback that receives
 // profiles from the fake browser process.
@@ -122,7 +150,7 @@
   // expected to be invoked. If not, first_snapshot_callback() returns a
   // callback that's expected not to run.
   //
-  // If `expect_sampled_profile` is true, HeapProfilerController::TakeSnapshot()
+  // If `expected_sampled_profiles` > 0, HeapProfilerController::TakeSnapshot()
   // should find a sample to pass to CallStackProfileBuilder, so
   // collector_callback() returns a callback that's expected to be invoked. It
   // will also run the given `profile_collector_callback`. If not,
@@ -133,7 +161,7 @@
   // in another process, so other_process_callback() will return a callback to
   // invoke for this.
   ScopedCallbacks(bool expect_take_snapshot,
-                  bool expect_sampled_profile,
+                  size_t expected_sampled_profiles,
                   bool use_other_process_callback,
                   ProfileCollectorCallback profile_collector_callback,
                   base::OnceClosure quit_closure) {
@@ -141,9 +169,7 @@
     if (expect_take_snapshot) {
       num_callbacks += 1;
     }
-    if (expect_sampled_profile) {
-      num_callbacks += 1;
-    }
+    num_callbacks += expected_sampled_profiles;
     if (use_other_process_callback) {
       // The test should invoke other_process_snapshot_callback() to simulate a
       // snapshot in another process.
@@ -172,19 +198,19 @@
         });
 
     collector_callback_ = base::BindLambdaForTesting(
-        [this, expect_sampled_profile,
+        [this, expected_sampled_profiles,
          callback = std::move(profile_collector_callback)](
             base::TimeTicks time_ticks, metrics::SampledProfile profile) {
-          if (!expect_sampled_profile) {
+          if (expected_sampled_profiles == 0) {
             FAIL() << "ProfileCollectorCallback called unexpectedly.";
           }
           collector_count_++;
-          if (collector_count_ == 1) {
+          if (collector_count_ <= expected_sampled_profiles) {
             std::move(callback).Run(time_ticks, profile);
             barrier_closure_.Run();
             return;
           }
-          if (collector_count_ == 2) {
+          if (collector_count_ == expected_sampled_profiles + 1) {
             FAIL() << "ProfileCollectorCallback invoked too many times.";
           }
         });
@@ -240,9 +266,8 @@
 
 class ProfilerSetUpMixin {
  public:
-  ProfilerSetUpMixin(
-      const std::vector<base::test::FeatureRefAndParams>& enabled_features,
-      const std::vector<base::test::FeatureRef>& disabled_features) {
+  ProfilerSetUpMixin(const std::vector<FeatureRefAndParams>& enabled_features,
+                     const std::vector<FeatureRef>& disabled_features) {
     // ScopedFeatureList must be initialized in the constructor, before any
     // threads are started.
     feature_list_.InitWithFeaturesAndParameters(enabled_features,
@@ -255,9 +280,6 @@
 
   ~ProfilerSetUpMixin() = default;
 
-  ProfilerSetUpMixin(const ProfilerSetUpMixin&) = delete;
-  ProfilerSetUpMixin& operator=(const ProfilerSetUpMixin&) = delete;
-
   base::test::TaskEnvironment& task_env() { return task_environment_; }
 
  private:
@@ -273,9 +295,224 @@
   base::test::ScopedFeatureList feature_list_;
 
   base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME,
+      base::test::TaskEnvironment::MainThreadType::IO};
 };
 
+#if ENABLE_MULTIPROCESS_TESTS
+
+constexpr char kTestChildTypeSwitch[] = "heap-profiler-test-child-type";
+constexpr char kTestNumAllocationsSwitch[] =
+    "heap-profiler-test-num-allocations";
+
+// Runs the heap profiler in a multiprocess test child. This is used instead of
+// HeapProfilerControllerTest::CreateHeapProfiler() in tests that create real
+// child processes. (Most tests run only in the test main process and pretend
+// that it's the Chrome browser process or a Chrome child process.)
+class MultiprocessTestChild final : public mojom::TestConnector,
+                                    public ProfilerSetUpMixin {
+ public:
+  MultiprocessTestChild(
+      const std::vector<FeatureRefAndParams>& enabled_features,
+      const std::vector<FeatureRef>& disabled_features)
+      : ProfilerSetUpMixin(enabled_features, disabled_features),
+        quit_closure_(task_env().QuitClosure()) {}
+
+  ~MultiprocessTestChild() final = default;
+
+  MultiprocessTestChild(const MultiprocessTestChild&) = delete;
+  MultiprocessTestChild& operator=(const MultiprocessTestChild&) = delete;
+
+  void RunTestInChild() {
+    // Get the process type and number of allocations to simulate.
+    const base::CommandLine* command_line =
+        base::CommandLine::ForCurrentProcess();
+    ASSERT_TRUE(command_line);
+    int process_type = 0;
+    ASSERT_TRUE(base::StringToInt(
+        command_line->GetSwitchValueASCII(kTestChildTypeSwitch),
+        &process_type));
+    int num_allocations = 0;
+    ASSERT_TRUE(base::StringToInt(
+        command_line->GetSwitchValueASCII(kTestNumAllocationsSwitch),
+        &num_allocations));
+
+    // Set up mojo support and attach to the parent's pipe.
+    mojo::core::ScopedIPCSupport enable_mojo(
+        base::SingleThreadTaskRunner::GetCurrentDefault(),
+        mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+    mojo::IncomingInvitation invitation = mojo::IncomingInvitation::Accept(
+        mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(
+            *command_line));
+
+    // Handle the TestConnector::Connect() message that will connect the
+    // SnapshotController and CallStackProfileCollector interfaces.
+    mojo::PendingReceiver<mojom::TestConnector> pending_receiver(
+        invitation.ExtractMessagePipe(0));
+    mojo::Receiver<mojom::TestConnector> receiver(this,
+                                                  std::move(pending_receiver));
+
+    // Start the heap profiler and wait for TakeSnapshot() messages from the
+    // parent.
+    HeapProfilerController controller(version_info::Channel::STABLE,
+                                      static_cast<ProcessType>(process_type));
+    controller.SuppressRandomnessForTesting();
+    ASSERT_TRUE(controller.IsEnabled());
+    controller.StartIfEnabled();
+
+    // Make a fixed number of allocations at different addresses to include in
+    // snapshots. No need to free since the process will exit after the test.
+    for (int i = 0; i < num_allocations; ++i) {
+      base::PoissonAllocationSampler::Get()->OnAllocation(
+          AllocationNotificationData(reinterpret_cast<void*>(0x1337 + i),
+                                     kAllocationSize, nullptr,
+                                     AllocationSubsystem::kManualForTesting));
+    }
+
+    // Loop until the TestConnector::Disconnect() message.
+    task_env().RunUntilQuit();
+  }
+
+  // mojom::TestConnector:
+
+  void Connect(
+      mojo::PendingReceiver<mojom::SnapshotController> receiver,
+      mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> remote,
+      base::OnceClosure done_callback) final {
+    // Create full ChildProcessSnapshotController and
+    // ChildCallStackProfileCollector instances to send snapshots to the parent
+    // process.
+    ChildProcessSnapshotController::CreateSelfOwnedReceiver(
+        std::move(receiver));
+    metrics::CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
+        std::move(remote));
+    std::move(done_callback).Run();
+  }
+
+  void Disconnect() final { std::move(quit_closure_).Run(); }
+
+ private:
+  base::OnceClosure quit_closure_;
+};
+
+// Manages a set of multiprocess test children and mojo connections to them.
+// Created in test cases in the parent process.
+class MultiprocessTestParent {
+ public:
+  MultiprocessTestParent() = default;
+
+  MultiprocessTestParent(const MultiprocessTestParent&) = delete;
+  MultiprocessTestParent& operator=(const MultiprocessTestParent&) = delete;
+
+  ~MultiprocessTestParent() {
+    // Tell all children to stop profiling and wait for them to exit.
+    for (auto& connector : test_connectors_) {
+      connector->Disconnect();
+    }
+    for (const auto& process : child_processes_) {
+      int exit_code = 0;
+      EXPECT_TRUE(base::WaitForMultiprocessTestChildExit(
+          process, TestTimeouts::action_timeout(), &exit_code));
+      EXPECT_EQ(exit_code, 0);
+    }
+  }
+
+  // Waits until `num_children` are connected, then starts profiling the parent
+  // process with `controller`.
+  void StartHeapProfilingWhenChildrenConnected(
+      size_t num_children,
+      HeapProfilerController* controller) {
+    // StartIfEnabled() needs to run on the current sequence no matter what
+    // thread mojo calls `on_child_connected_closure_` from.
+    on_child_connected_closure_ = base::BarrierClosure(
+        num_children, base::BindPostTaskToCurrentDefault(
+                          base::BindLambdaForTesting([this, controller] {
+                            // Make sure all children connected successfully.
+                            ASSERT_EQ(test_connectors_.size(),
+                                      child_processes_.size());
+                            EXPECT_TRUE(controller->StartIfEnabled());
+                          })));
+  }
+
+  // Called from HeapProfilerController::AppendCommandLineSwitchForChildProcess
+  // with `connector_id` and `receiver`, plus a `remote` added by the test.
+  // `connector_id` is the id of a mojo TestConnector interface for the process.
+  // In production this parameter is the child process id.
+  void BindTestConnector(
+      int connector_id,
+      mojo::PendingReceiver<mojom::SnapshotController> receiver,
+      mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> remote) {
+    // BrowserProcessSnapshotController holds the remote end of the
+    // mojom::SnapshotController, and the test fixture holds the receiver end of
+    // the CallStackProfileCollector. Pass the other ends to the test child.
+    // `on_child_connected_closure_` will be called with the response.
+    mojom::TestConnector* connector = test_connectors_.Get(
+        mojo::RemoteSetElementId::FromUnsafeValue(connector_id));
+    ASSERT_TRUE(connector);
+    connector->Connect(std::move(receiver), std::move(remote),
+                       on_child_connected_closure_);
+  }
+
+  // Launches a multiprocess test child and registers it with `controller`.
+  // The child will simulate a process of type `process_type` and make
+  // `num_allocations` memory allocations to report in heap snapshots.
+  void LaunchTestChild(HeapProfilerController* controller,
+                       ProcessType process_type,
+                       int num_allocations) {
+    base::LaunchOptions launch_options;
+    base::CommandLine child_command_line =
+        base::GetMultiProcessTestChildBaseCommandLine();
+    child_command_line.AppendSwitchASCII(
+        kTestChildTypeSwitch,
+        base::NumberToString(static_cast<int>(process_type)));
+    child_command_line.AppendSwitchASCII(kTestNumAllocationsSwitch,
+                                         base::NumberToString(num_allocations));
+
+    // Attach a mojo channel to the child.
+    mojo::PlatformChannel channel;
+    channel.PrepareToPassRemoteEndpoint(&launch_options, &child_command_line);
+    mojo::OutgoingInvitation invitation;
+    mojo::PendingRemote<mojom::TestConnector> pending_connector(
+        invitation.AttachMessagePipe(0), 0);
+    mojo::RemoteSetElementId connector_id =
+        test_connectors_.Add(std::move(pending_connector));
+
+    // In production this only connects the parent end of the SnapshotController
+    // since content::ChildProcessHost brokers the interface with the child. For
+    // the test, smuggle the id of a TestConnector to broker the interface by
+    // pretending it's the child process id.
+    controller->AppendCommandLineSwitchForChildProcess(
+        &child_command_line, process_type, connector_id.GetUnsafeValue());
+
+    base::Process child_process = base::SpawnMultiProcessTestChild(
+        "HeapProfilerControllerChildMain", child_command_line, launch_options);
+    ASSERT_TRUE(child_process.IsValid());
+
+    // Finish connecting the mojo channel. This passes the other end of the
+    // TestConnector message pipe to the child.
+    channel.RemoteProcessLaunchAttempted();
+    mojo::OutgoingInvitation::Send(std::move(invitation),
+                                   child_process.Handle(),
+                                   channel.TakeLocalEndpoint());
+
+    child_processes_.push_back(std::move(child_process));
+  }
+
+ private:
+  // All child processes started by the test. If a child dies the process will
+  // become invalid but remain in this list.
+  std::vector<base::Process> child_processes_;
+
+  // Test interface for controlling each child process. If a child dies the
+  // interface will be disconnected and removed from this set.
+  mojo::RemoteSet<mojom::TestConnector> test_connectors_;
+
+  // Closure to call whenever a child process is finished connecting.
+  base::RepeatingClosure on_child_connected_closure_;
+};
+
+#endif  // ENABLE_MULTIPROCESS_TESTS
+
 class MockSnapshotController : public mojom::SnapshotController {
  public:
   MOCK_METHOD(void, TakeSnapshot, (), (override));
@@ -448,7 +685,7 @@
         ResetChildCallStackProfileCollectorForTesting();
   }
 
-  // Creates a HeapProfilerController and mocks profiling a process of type
+  // Creates a HeapProfilerController to mock profiling a process of type
   // `process_type` on `channel`. The test should pass `expect_enabled` as true
   // if heap profiling should be enabled in this test setup.
   //
@@ -456,13 +693,15 @@
   // HeapProfilerController::TakeSnapshot() is called, even if it doesn't
   // collect a profile. `collector_callback` will be invoked whenever
   // TakeSnapshot() passes a profile to CallStackProfileBuilder.
-  void StartHeapProfiling(
+  //
+  // The test must call StartIfEnabled() after this to start profiling.
+  void CreateHeapProfiler(
       version_info::Channel channel,
       ProcessType process_type,
       bool expect_enabled,
       base::OnceClosure first_snapshot_callback = base::DoNothing(),
       ProfileCollectorCallback collector_callback = base::DoNothing()) {
-    ASSERT_FALSE(controller_) << "StartHeapProfiling called twice";
+    ASSERT_FALSE(controller_) << "CreateHeapProfiler called twice";
     switch (process_type) {
       case ProcessType::kBrowser:
         expected_process_ = metrics::Process::BROWSER_PROCESS;
@@ -471,13 +710,17 @@
         break;
       case ProcessType::kUtility:
         expected_process_ = metrics::Process::UTILITY_PROCESS;
-        ConnectRemoteProfileCollector(std::move(collector_callback));
+        metrics::CallStackProfileBuilder::
+            SetParentProfileCollectorForChildProcess(
+                AddTestProfileCollector(std::move(collector_callback)));
         break;
       default:
         // Connect up the profile collector even though we expect the heap
         // profiler not to start, so that the test environment is complete.
         expected_process_ = metrics::Process::UNKNOWN_PROCESS;
-        ConnectRemoteProfileCollector(std::move(collector_callback));
+        metrics::CallStackProfileBuilder::
+            SetParentProfileCollectorForChildProcess(
+                AddTestProfileCollector(std::move(collector_callback)));
         break;
     }
 
@@ -491,6 +734,19 @@
 
     EXPECT_EQ(HeapProfilerController::GetInstance(), controller_.get());
     EXPECT_EQ(controller_->IsEnabled(), expect_enabled);
+  }
+
+  // Creates a HeapProfilerController with CreateHeapProfiler() and starts
+  // profiling.
+  void StartHeapProfiling(
+      version_info::Channel channel,
+      ProcessType process_type,
+      bool expect_enabled,
+      base::OnceClosure first_snapshot_callback = base::DoNothing(),
+      ProfileCollectorCallback collector_callback = base::DoNothing()) {
+    CreateHeapProfiler(channel, process_type, expect_enabled,
+                       std::move(first_snapshot_callback),
+                       std::move(collector_callback));
     EXPECT_EQ(controller_->StartIfEnabled(), expect_enabled);
   }
 
@@ -506,14 +762,17 @@
                              AllocationSubsystem::kManualForTesting));
   }
 
-  void ConnectRemoteProfileCollector(
-      ProfileCollectorCallback collector_callback) {
+  // Creates a TestCallStackProfileCollector that accepts callstacks from the
+  // and passes them to `collector_callback`. Returns a remote for the profiler
+  // to pass the callstacks to.
+  mojo::PendingRemote<metrics::mojom::CallStackProfileCollector>
+  AddTestProfileCollector(ProfileCollectorCallback collector_callback) {
     mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> remote;
-    mojo::MakeSelfOwnedReceiver(std::make_unique<TestCallStackProfileCollector>(
-                                    std::move(collector_callback)),
-                                remote.InitWithNewPipeAndPassReceiver());
-    metrics::CallStackProfileBuilder::SetParentProfileCollectorForChildProcess(
-        std::move(remote));
+    profile_collector_receivers_.Add(
+        std::make_unique<TestCallStackProfileCollector>(
+            std::move(collector_callback)),
+        remote.InitWithNewPipeAndPassReceiver());
+    return remote;
   }
 
   ScopedCallbacks CreateScopedCallbacks(
@@ -521,7 +780,7 @@
       bool expect_sampled_profile,
       bool use_other_process_callback = false) {
     return ScopedCallbacks(
-        expect_take_snapshot, expect_sampled_profile,
+        expect_take_snapshot, expect_sampled_profile ? 1 : 0,
         use_other_process_callback,
         base::BindRepeating(&HeapProfilerControllerTest::RecordSampleReceived,
                             base::Unretained(this)),
@@ -543,6 +802,11 @@
   // background thread, but does not need to be atomic because the write happens
   // during a scheduled sample and the read happens well after that.
   bool sample_received_ = false;
+
+  // Receivers for callstack profiles. Each element of the set is a
+  // TestCallStackProfileCollecter and associated mojo::Receiver.
+  mojo::UniqueReceiverSet<metrics::mojom::CallStackProfileCollector>
+      profile_collector_receivers_;
 };
 
 // Basic tests only use the default feature params.
@@ -923,6 +1187,147 @@
   EXPECT_EQ(sample_received_, GetParam().include_zero_feature_enabled);
 }
 
+#if ENABLE_MULTIPROCESS_TESTS
+
+// End-to-end test of the HeapProfilerCentralControl feature with multiple child
+// processes.
+constexpr FeatureTestParams kMultipleChildConfigs[] = {
+    {
+        .supported_processes = {ProcessType::kBrowser, ProcessType::kUtility,
+                                ProcessType::kRenderer},
+        .include_zero_feature_enabled = true,
+        .central_control_feature_enabled = true,
+    },
+};
+
+using HeapProfilerControllerMultipleChildTest = HeapProfilerControllerTest;
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         HeapProfilerControllerMultipleChildTest,
+                         ::testing::ValuesIn(kMultipleChildConfigs));
+
+MULTIPROCESS_TEST_MAIN(HeapProfilerControllerChildMain) {
+  MultiprocessTestChild child(kMultipleChildConfigs[0].GetEnabledFeatures(),
+                              kMultipleChildConfigs[0].GetDisabledFeatures());
+  child.RunTestInChild();
+  return 0;
+}
+
+TEST_P(HeapProfilerControllerMultipleChildTest, EndToEnd) {
+  // Initialize mojo IPC support.
+  mojo::core::ScopedIPCSupport enable_mojo(
+      base::SingleThreadTaskRunner::GetCurrentDefault(),
+      mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+
+  // Process types to test. Each will make a different
+  // number of memory allocations so their reports are all different.
+  const std::map<ProcessType, size_t> kProcessesToTest{
+      {ProcessType::kBrowser, 0},
+      {ProcessType::kUtility, 1},
+      {ProcessType::kRenderer, 2},
+  };
+
+  // Create callbacks that store profiles from all processes in a vector.
+  std::vector<metrics::SampledProfile> received_profiles;
+  auto collector_callback = base::BindLambdaForTesting(
+      [&](base::TimeTicks, metrics::SampledProfile profile) {
+        received_profiles.push_back(std::move(profile));
+      });
+  ScopedCallbacks callbacks(
+      /*expect_take_snapshot=*/true,
+      /*expected_sampled_profiles=*/kProcessesToTest.size(),
+      /*use_other_process_callback=*/false, std::move(collector_callback),
+      task_env().QuitClosure());
+
+  // Snapshots from the children take real time to be passed back to the parent.
+  // The mock clock will advance to the next snapshot time while waiting, so
+  // stop profiling after the first snapshot by deleting the controller.
+  auto stop_after_first_snapshot_callback =
+      callbacks.first_snapshot_callback().Then(base::BindLambdaForTesting(
+          [this, task_runner = base::SequencedTaskRunner::GetCurrentDefault()] {
+            task_runner->DeleteSoon(FROM_HERE, controller_.release());
+          }));
+
+  CreateHeapProfiler(version_info::Channel::STABLE, ProcessType::kBrowser,
+                     /*expect_enabled=*/true,
+                     std::move(stop_after_first_snapshot_callback),
+                     callbacks.collector_callback());
+  ASSERT_TRUE(controller_);
+
+  // Start all processes in `kProcessesToTest` except the browser.
+  MultiprocessTestParent test_parent;
+  test_parent.StartHeapProfilingWhenChildrenConnected(
+      kProcessesToTest.size() - 1, controller_.get());
+
+  // On every process launch, create a TestCallStackProfileCollector to collect
+  // profiles from the child. BrowserProcessSnapshotController will create a
+  // SnapshotController to trigger snapshots in the child.
+  auto* browser_snapshot_controller =
+      controller_->GetBrowserProcessSnapshotController();
+  ASSERT_TRUE(browser_snapshot_controller);
+  auto binder_callback = base::BindLambdaForTesting(
+      [&](int id, mojo::PendingReceiver<mojom::SnapshotController> receiver) {
+        mojo::PendingRemote<metrics::mojom::CallStackProfileCollector> remote =
+            AddTestProfileCollector(callbacks.collector_callback());
+        test_parent.BindTestConnector(id, std::move(receiver),
+                                      std::move(remote));
+      });
+  browser_snapshot_controller->SetBindRemoteForChildProcessCallback(
+      std::move(binder_callback));
+
+  for (const auto [process_type, num_allocations] : kProcessesToTest) {
+    if (process_type != ProcessType::kBrowser) {
+      test_parent.LaunchTestChild(controller_.get(), process_type,
+                                  num_allocations);
+    }
+  }
+
+  // Loop until all children are connected and all processes send snapshots.
+  task_env().RunUntilQuit();
+
+  // GMock matcher that tests that the given CallStackProfile contains `count`
+  // stack samples, each with weight `avg_weight`.
+  auto call_stack_profile_matches = [](size_t count, size_t avg_weight) {
+    using StackSample = metrics::CallStackProfile::StackSample;
+    return Property(
+        "stack_sample", &metrics::CallStackProfile::stack_sample,
+        Conditional(
+            count > 0,
+            // The test makes allocations at addresses without symbols, so
+            // they're all counted in the same stack frame.
+            ElementsAre(AllOf(
+                Property("count", &StackSample::count, count),
+                Property("weight", &StackSample::weight, count * avg_weight))),
+            // No allocations means no stack frames.
+            IsEmpty()));
+  };
+
+  // GMock matcher that tests that the given SampledProfile is a heap snapshot
+  // for the given `process_type` containing `count` stack
+  // samples, each with weight `avg_weight`.
+  auto sampled_profile_matches = [&](metrics::Process process_type,
+                                     size_t count, size_t avg_weight) {
+    return AllOf(
+        Property("trigger_event", &metrics::SampledProfile::trigger_event,
+                 metrics::SampledProfile::PERIODIC_HEAP_COLLECTION),
+        Property("process", &metrics::SampledProfile::process, process_type),
+        Property("call_stack_profile",
+                 &metrics::SampledProfile::call_stack_profile,
+                 call_stack_profile_matches(count, avg_weight)));
+  };
+
+  EXPECT_THAT(
+      received_profiles,
+      UnorderedElementsAre(
+          sampled_profile_matches(metrics::Process::BROWSER_PROCESS, 0, 0),
+          sampled_profile_matches(metrics::Process::UTILITY_PROCESS, 1,
+                                  kAllocationSize),
+          sampled_profile_matches(metrics::Process::RENDERER_PROCESS, 2,
+                                  kAllocationSize)));
+}
+
+#endif  // ENABLE_MULTIPROCESS_TESTS
+
 }  // namespace
 
 }  // namespace heap_profiling
diff --git a/components/heap_profiling/in_process/mojom/test_connector.mojom b/components/heap_profiling/in_process/mojom/test_connector.mojom
new file mode 100644
index 0000000..1d5228a
--- /dev/null
+++ b/components/heap_profiling/in_process/mojom/test_connector.mojom
@@ -0,0 +1,23 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module heap_profiling.mojom;
+
+import "components/heap_profiling/in_process/mojom/snapshot_controller.mojom";
+import "components/metrics/public/mojom/call_stack_profile_collector.mojom";
+
+// Bootstraps heap_profiling.mojom.SnapshotController (browser->child) and
+// metrics.mojom.CallStacksProfileCollector (child->browser) connections in a
+// multiprocess test.
+interface TestConnector {
+  // Passes interfaces to the child, which should reply once they're bound.
+  Connect(
+      pending_receiver<heap_profiling.mojom.SnapshotController>
+          snapshot_controller,
+      pending_remote<metrics.mojom.CallStackProfileCollector>
+          profile_collector) => ();
+
+  // Tells the child the test is over.
+  Disconnect();
+};
diff --git a/components/management_strings.grdp b/components/management_strings.grdp
index c4b2b1a3..8d691cf73 100644
--- a/components/management_strings.grdp
+++ b/components/management_strings.grdp
@@ -287,6 +287,29 @@
     <message name="IDS_MANAGEMENT_LEGACY_TECH_REPORT" desc="Message explaining that browser will upload a report when a legacy technology is used for a webpage" formatter_data="android_java">
       A limited list of URLs of pages you visit where <ph name="BEGIN_LINK">&lt;a target="_blank" href="https://chromestatus.com/features#browsers.chrome.status%3A%22Deprecated%22" &gt;</ph>legacy technology events<ph name="END_LINK">&lt;/a&gt;</ph> are occuring.
     </message>
+
+    <!--Profile reporting management message-->
+    <if expr="not is_android">
+      <message name="IDS_MANAGEMENT_PROFILE_REPORTING_EXPLANATION" desc="Message explaining browser reporting" formatter_data="android_java">
+        Managed profile, browser, and some device information is accessible to your administrator. They can see information such as the following:
+      </message>
+      <message name="IDS_MANAGEMENT_PROFILE_REPORTING_OVERVIEW" desc="Item in a list of information viewable by the enterprise admin, indicating that the admin can view the user's work profile overview." formatter_data="android_java">
+        Work profile overview
+      </message>
+      <message name="IDS_MANAGEMENT_PROFILE_REPORTING_USERNAME" desc="Item in a list of information viewable by the enterprise admin, indicating that the admin can view the user's work profile username." formatter_data="android_java">
+        Work profile information (such as your work profile username)
+      </message>
+      <message name="IDS_MANAGEMENT_PROFILE_REPORTING_BROWSER" desc="Item in a list of information viewable by the enterprise admin, indicating that the admin can view the device information that user's work profile belong to" formatter_data="android_java">
+        Browser and device OS information (such as the browser &amp; OS versions)
+      </message>
+      <message name="IDS_MANAGEMENT_PROFILE_REPORTING_EXTENSION" desc="Item in a list of information viewable by the enterprise admin, indicating that the admin can view user's work profile extensions" formatter_data="android_java">
+        Installed apps &amp; extensions in your work profile
+      </message>
+      <message name="IDS_MANAGEMENT_PROFILE_REPORTING_POLICY" desc="Item in a list of information viewable by the enterprise admin, indicating that the admin can viewuser's work profile policies" formatter_data="android_java">
+        Applied browser policies in your work profile
+      </message>
+    </if>
+
     <!-- Strings related to Chrome Enterprise Connectors -->
     <message name="IDS_MANAGEMENT_THREAT_PROTECTION" desc="Title of the Chrome Enterprise Connectors section of the page">
       Chrome Enterprise Connectors
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_BROWSER.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_BROWSER.png.sha1
new file mode 100644
index 0000000..a50355c3
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_BROWSER.png.sha1
@@ -0,0 +1 @@
+8394488b80db3c7498de2247695e860f1a8bf731
\ No newline at end of file
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_EXPLANATION.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_EXPLANATION.png.sha1
new file mode 100644
index 0000000..a50355c3
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_EXPLANATION.png.sha1
@@ -0,0 +1 @@
+8394488b80db3c7498de2247695e860f1a8bf731
\ No newline at end of file
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_EXTENSION.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_EXTENSION.png.sha1
new file mode 100644
index 0000000..a50355c3
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_EXTENSION.png.sha1
@@ -0,0 +1 @@
+8394488b80db3c7498de2247695e860f1a8bf731
\ No newline at end of file
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_OVERVIEW.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_OVERVIEW.png.sha1
new file mode 100644
index 0000000..a50355c3
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_OVERVIEW.png.sha1
@@ -0,0 +1 @@
+8394488b80db3c7498de2247695e860f1a8bf731
\ No newline at end of file
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_POLICY.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_POLICY.png.sha1
new file mode 100644
index 0000000..a50355c3
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_POLICY.png.sha1
@@ -0,0 +1 @@
+8394488b80db3c7498de2247695e860f1a8bf731
\ No newline at end of file
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_USERNAME.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_USERNAME.png.sha1
new file mode 100644
index 0000000..a50355c3
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_PROFILE_REPORTING_USERNAME.png.sha1
@@ -0,0 +1 @@
+8394488b80db3c7498de2247695e860f1a8bf731
\ No newline at end of file
diff --git a/components/ml/webnn/graph_validation_utils.h b/components/ml/webnn/graph_validation_utils.h
index 818674b..d1e5351 100644
--- a/components/ml/webnn/graph_validation_utils.h
+++ b/components/ml/webnn/graph_validation_utils.h
@@ -70,7 +70,7 @@
 
 static constexpr DataTypeConstraintSet kGatherOperatorIndexDataTypes = {
     Operand::DataType::kInt32, Operand::DataType::kUint32,
-    Operand::DataType::kInt64};
+    Operand::DataType::kInt64, Operand::DataType::kUint64};
 
 }  // namespace DataTypeConstraint
 
diff --git a/components/omnibox/browser/shortcuts_database.cc b/components/omnibox/browser/shortcuts_database.cc
index aaf4b79..94f4938 100644
--- a/components/omnibox/browser/shortcuts_database.cc
+++ b/components/omnibox/browser/shortcuts_database.cc
@@ -10,7 +10,6 @@
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
-#include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "base/uuid.h"
 #include "components/omnibox/browser/autocomplete_match_type.h"
@@ -32,32 +31,22 @@
 const int kCompatibleVersionNumber = 1;
 
 void BindShortcutToStatement(const ShortcutsDatabase::Shortcut& shortcut,
-                             sql::Statement* s) {
+                             sql::Statement& s) {
   DCHECK(base::Uuid::ParseCaseInsensitive(shortcut.id).is_valid());
-  s->BindString(0, shortcut.id);
-  s->BindString16(1, shortcut.text);
-  s->BindString16(2, shortcut.match_core.fill_into_edit);
-  s->BindString(3, shortcut.match_core.destination_url.spec());
-  s->BindInt(4, base::checked_cast<int>(shortcut.match_core.document_type));
-  s->BindString16(5, shortcut.match_core.contents);
-  s->BindString(6, shortcut.match_core.contents_class);
-  s->BindString16(7, shortcut.match_core.description);
-  s->BindString(8, shortcut.match_core.description_class);
-  s->BindInt(9, base::checked_cast<int>(shortcut.match_core.transition));
-  s->BindInt(10, base::checked_cast<int>(shortcut.match_core.type));
-  s->BindString16(11, shortcut.match_core.keyword);
-  s->BindTime(12, shortcut.last_access_time);
-  s->BindInt(13, shortcut.number_of_hits);
-}
-
-bool DeleteShortcut(const char* field_name,
-                    const std::string& id,
-                    sql::Database& db) {
-  sql::Statement s(db.GetUniqueStatement(
-      base::StringPrintf("DELETE FROM omni_box_shortcuts WHERE %s = ?",
-                         field_name).c_str()));
-  s.BindString(0, id);
-  return s.Run();
+  s.BindString(0, shortcut.id);
+  s.BindString16(1, shortcut.text);
+  s.BindString16(2, shortcut.match_core.fill_into_edit);
+  s.BindString(3, shortcut.match_core.destination_url.spec());
+  s.BindInt(4, base::checked_cast<int>(shortcut.match_core.document_type));
+  s.BindString16(5, shortcut.match_core.contents);
+  s.BindString(6, shortcut.match_core.contents_class);
+  s.BindString16(7, shortcut.match_core.description);
+  s.BindString(8, shortcut.match_core.description_class);
+  s.BindInt(9, base::checked_cast<int>(shortcut.match_core.transition));
+  s.BindInt(10, base::checked_cast<int>(shortcut.match_core.type));
+  s.BindString16(11, shortcut.match_core.keyword);
+  s.BindTime(12, shortcut.last_access_time);
+  s.BindInt(13, shortcut.number_of_hits);
 }
 
 void DatabaseErrorCallback(sql::Database* db,
@@ -105,11 +94,9 @@
       type(type),
       keyword(keyword) {}
 
-ShortcutsDatabase::Shortcut::MatchCore::MatchCore(const MatchCore& other) =
-    default;
+ShortcutsDatabase::Shortcut::MatchCore::MatchCore(const MatchCore&) = default;
 
-ShortcutsDatabase::Shortcut::MatchCore::~MatchCore() {
-}
+ShortcutsDatabase::Shortcut::MatchCore::~MatchCore() = default;
 
 // ShortcutsDatabase::Shortcut ------------------------------------------------
 
@@ -141,11 +128,9 @@
       last_access_time(base::Time::Now()),
       number_of_hits(0) {}
 
-ShortcutsDatabase::Shortcut::Shortcut(const Shortcut& other) = default;
+ShortcutsDatabase::Shortcut::Shortcut(const Shortcut&) = default;
 
-ShortcutsDatabase::Shortcut::~Shortcut() {
-}
-
+ShortcutsDatabase::Shortcut::~Shortcut() = default;
 
 // ShortcutsDatabase ----------------------------------------------------------
 
@@ -171,43 +156,65 @@
 }
 
 bool ShortcutsDatabase::AddShortcut(const Shortcut& shortcut) {
-  sql::Statement s(db_.GetCachedStatement(
-      SQL_FROM_HERE,
-      "INSERT INTO omni_box_shortcuts (id, text, fill_into_edit, url, "
-          "document_type, contents, contents_class, description, "
-          "description_class, transition, type, keyword, last_access_time, "
-          "number_of_hits) "
-      "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)"));
-  BindShortcutToStatement(shortcut, &s);
+  static constexpr char kInsertSql[] =
+      // clang-format off
+      "INSERT INTO omni_box_shortcuts("
+        "id,text,fill_into_edit,url,document_type,contents,contents_class,"
+        "description,description_class,transition,type,keyword,"
+        "last_access_time,number_of_hits)"
+      "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
+  // clang-format on
+
+  sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, kInsertSql));
+  BindShortcutToStatement(shortcut, s);
   return s.Run();
 }
 
 bool ShortcutsDatabase::UpdateShortcut(const Shortcut& shortcut) {
-  sql::Statement s(db_.GetCachedStatement(
-      SQL_FROM_HERE,
-      "UPDATE omni_box_shortcuts SET id=?, text=?, fill_into_edit=?, url=?, "
-          "document_type=?, contents=?, contents_class=?, description=?, "
-          "description_class=?, transition=?, type=?, keyword=?, "
-          "last_access_time=?, number_of_hits=? WHERE id=?"));
-  BindShortcutToStatement(shortcut, &s);
+  static constexpr char kUpdateSql[] =
+      // clang-format off
+      "UPDATE omni_box_shortcuts "
+        "SET "
+          "id=?,text=?,fill_into_edit=?,url=?,"
+          "document_type=?,contents=?,contents_class=?,description=?,"
+          "description_class=?,transition=?,type=?,keyword=?,"
+          "last_access_time=?,number_of_hits=? "
+        "WHERE id=?";
+  // clang-format on
+
+  sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE, kUpdateSql));
+  BindShortcutToStatement(shortcut, s);
   s.BindString(14, shortcut.id);
   return s.Run();
 }
 
 bool ShortcutsDatabase::DeleteShortcutsWithIDs(
     const ShortcutIDs& shortcut_ids) {
-  bool success = true;
-  db_.BeginTransaction();
-  for (auto it(shortcut_ids.begin()); it != shortcut_ids.end(); ++it) {
-    success &= DeleteShortcut("id", *it, db_);
+  sql::Transaction transaction(&db_);
+  if (!transaction.Begin()) {
+    return false;
   }
-  db_.CommitTransaction();
-  return success;
+
+  sql::Statement s(
+      db_.GetUniqueStatement("DELETE FROM omni_box_shortcuts WHERE id=?"));
+
+  for (const std::string& id : shortcut_ids) {
+    s.Reset(/*clear_bound_vars=*/true);
+    s.BindString(0, id);
+    if (!s.Run()) {
+      return false;
+    }
+  }
+
+  return transaction.Commit();
 }
 
 bool ShortcutsDatabase::DeleteShortcutsWithURL(
     const std::string& shortcut_url_spec) {
-  return DeleteShortcut("url", shortcut_url_spec, db_);
+  sql::Statement s(
+      db_.GetUniqueStatement("DELETE FROM omni_box_shortcuts WHERE url=?"));
+  s.BindString(0, shortcut_url_spec);
+  return s.Run();
 }
 
 bool ShortcutsDatabase::DeleteAllShortcuts() {
@@ -220,13 +227,18 @@
 
 void ShortcutsDatabase::LoadShortcuts(GuidToShortcutMap* shortcuts) {
   DCHECK(shortcuts);
-  sql::Statement s(db_.GetCachedStatement(
-      SQL_FROM_HERE,
-      "SELECT id, text, fill_into_edit, url, document_type, contents, "
-          "contents_class, description, description_class, transition, type, "
-          "keyword, last_access_time, number_of_hits FROM omni_box_shortcuts"));
-
   shortcuts->clear();
+
+  static constexpr char kSelectSql[] =
+      // clang-format off
+      "SELECT id,text,fill_into_edit,url,document_type,contents,contents_class,"
+        "description,description_class,transition,type,keyword,"
+        "last_access_time,number_of_hits "
+      "FROM omni_box_shortcuts";
+  // clang-format on
+
+  sql::Statement s(db_.GetUniqueStatement(kSelectSql));
+
   while (s.Step()) {
     // Some users have corrupt data in their SQL database. That causes crashes.
     // Therefore, validate the integral values first. https://crbug.com/1024114
@@ -268,8 +280,7 @@
   }
 }
 
-ShortcutsDatabase::~ShortcutsDatabase() {
-}
+ShortcutsDatabase::~ShortcutsDatabase() = default;
 
 bool ShortcutsDatabase::EnsureTable() {
   if (!db_.DoesTableExist("omni_box_shortcuts"))
@@ -296,9 +307,7 @@
   }
 
   for (int i = current_version + 1; i <= kCurrentVersionNumber; ++i) {
-    if (!DoMigration(i))
-      return false;
-    if (!meta_table_.SetVersionNumber(i)) {
+    if (!DoMigration(i)) {
       return false;
     }
   }
@@ -307,76 +316,68 @@
 }
 
 bool ShortcutsDatabase::DoMigration(int version) {
-  // Perform migrations in transactions to avoid incomplete migrations.
+  // The migration must be performed transactionally with the meta table
+  // update, or else one of them can be applied without the other, leading to
+  // incorrect logic on subsequent use.
   sql::Transaction transaction(&db_);
+  if (!transaction.Begin()) {
+    return false;
+  }
 
   switch (version) {
     case -1:
       // When there is no existing table, skip iterative migration; instead,
       // migrate to the latest version.
-      return transaction.Begin() &&
-             meta_table_.Init(&db_, kCurrentVersionNumber,
+      return meta_table_.Init(&db_, kCurrentVersionNumber,
                               kCompatibleVersionNumber) &&
              db_.Execute(
-                 "CREATE TABLE omni_box_shortcuts (id VARCHAR PRIMARY KEY, "
-                 "text VARCHAR, fill_into_edit VARCHAR, url VARCHAR, "
-                 "document_type INTEGER, contents VARCHAR, "
-                 "contents_class VARCHAR, description VARCHAR, "
-                 "description_class VARCHAR, transition INTEGER, type INTEGER, "
-                 "keyword VARCHAR, last_access_time INTEGER, "
+                 "CREATE TABLE omni_box_shortcuts(id VARCHAR PRIMARY KEY,"
+                 "text VARCHAR,fill_into_edit VARCHAR,url VARCHAR,"
+                 "document_type INTEGER,contents VARCHAR,"
+                 "contents_class VARCHAR,description VARCHAR,"
+                 "description_class VARCHAR,transition INTEGER,type INTEGER,"
+                 "keyword VARCHAR,last_access_time INTEGER,"
                  "number_of_hits INTEGER)") &&
              transaction.Commit();
     case 0:
+      static_assert(static_cast<int>(ui::PAGE_TRANSITION_TYPED) == 1);
+      static_assert(static_cast<int>(AutocompleteMatchType::HISTORY_TITLE) ==
+                    2);
+
       // Version pre-0 of the shortcuts table lacked the fill_into_edit,
       // transition type, and keyword columns.
-      return transaction.Begin() &&
-             db_.Execute(
+      return db_.Execute(
                  "ALTER TABLE omni_box_shortcuts "
                  "ADD COLUMN fill_into_edit VARCHAR") &&
-             db_.Execute(
-                 "UPDATE omni_box_shortcuts SET fill_into_edit = url") &&
+             db_.Execute("UPDATE omni_box_shortcuts SET fill_into_edit=url") &&
              db_.Execute(
                  "ALTER TABLE omni_box_shortcuts "
                  "ADD COLUMN transition INTEGER") &&
-             db_.Execute(base::StringPrintf(
-                             "UPDATE omni_box_shortcuts SET transition = %d",
-                             static_cast<int>(ui::PAGE_TRANSITION_TYPED))
-                             .c_str()) &&
+             db_.Execute("UPDATE omni_box_shortcuts SET transition=1") &&
              db_.Execute(
                  "ALTER TABLE omni_box_shortcuts ADD COLUMN type INTEGER") &&
+             db_.Execute("UPDATE omni_box_shortcuts SET type=2") &&
              db_.Execute(
-                 base::StringPrintf(
-                     "UPDATE omni_box_shortcuts SET type = %d",
-                     static_cast<int>(AutocompleteMatchType::HISTORY_TITLE))
-                     .c_str()) &&
-             db_.Execute(
-                 "ALTER TABLE omni_box_shortcuts "
-                 "ADD COLUMN keyword VARCHAR") &&
+                 "ALTER TABLE omni_box_shortcuts ADD COLUMN keyword VARCHAR") &&
              transaction.Commit();
     case 1:
-      return transaction.Begin() &&
-             // Create the MetaTable.
-             meta_table_.Init(&db_, 1, kCompatibleVersionNumber) &&
-             // Migrate old SEARCH_OTHER_ENGINE values to the new type value.
-             db_.Execute(
-                 "UPDATE omni_box_shortcuts SET type = 13 WHERE type = 9") &&
-             // Migrate old EXTENSION_APP values to the new type value.
-             db_.Execute(
-                 "UPDATE omni_box_shortcuts SET type = 14 WHERE type = 10") &&
-             // Migrate old CONTACT values to the new type value.
-             db_.Execute(
-                 "UPDATE omni_box_shortcuts SET type = 15 WHERE type = 11") &&
-             // Migrate old BOOKMARK_TITLE values to the new type value.
-             db_.Execute(
-                 "UPDATE omni_box_shortcuts SET type = 16 WHERE type = 12") &&
-             transaction.Commit();
+      return  // Create the MetaTable.
+          meta_table_.Init(&db_, 1, kCompatibleVersionNumber) &&
+          // Migrate old SEARCH_OTHER_ENGINE values to the new type value.
+          db_.Execute("UPDATE omni_box_shortcuts SET type=13 WHERE type=9") &&
+          // Migrate old EXTENSION_APP values to the new type value.
+          db_.Execute("UPDATE omni_box_shortcuts SET type=14 WHERE type=10") &&
+          // Migrate old CONTACT values to the new type value.
+          db_.Execute("UPDATE omni_box_shortcuts SET type=15 WHERE type=11") &&
+          // Migrate old BOOKMARK_TITLE values to the new type value.
+          db_.Execute("UPDATE omni_box_shortcuts SET type=16 WHERE type=12") &&
+          transaction.Commit();
     case 2:
       // Version 1 of the shortcuts table lacked the document_type column.
-      return transaction.Begin() &&
-             db_.Execute(
+      return db_.Execute(
                  "ALTER TABLE omni_box_shortcuts "
                  "ADD COLUMN document_type INTEGER") &&
-             transaction.Commit();
+             meta_table_.SetVersionNumber(version) && transaction.Commit();
     default:
       return false;
   }
diff --git a/components/optimization_guide/core/prediction_manager.cc b/components/optimization_guide/core/prediction_manager.cc
index 891b39ec..5b870db 100644
--- a/components/optimization_guide/core/prediction_manager.cc
+++ b/components/optimization_guide/core/prediction_manager.cc
@@ -148,7 +148,11 @@
          model_metadata.type_url() ==
              "type.googleapis.com/"
              "google.internal.chrome.optimizationguide.v1."
-             "HistoryClustersModuleRankingModelMetadata";
+             "HistoryClustersModuleRankingModelMetadata" ||
+         model_metadata.type_url() ==
+             "type.googleapis.com/"
+             "google.internal.chrome.optimizationguide.v1."
+             "OnDeviceBaseModelMetadata";
 }
 
 void RecordModelAvailableAtRegistration(
@@ -199,8 +203,9 @@
 }
 
 PredictionManager::~PredictionManager() {
-  if (prediction_model_download_manager_)
+  if (prediction_model_download_manager_) {
     prediction_model_download_manager_->RemoveObserver(this);
+  }
 }
 
 void PredictionManager::Initialize(
@@ -352,8 +357,9 @@
       static_cast<int>(
           *base_model_info.supported_model_engine_versions().begin()));
 
-  if (switches::IsModelOverridePresent())
+  if (switches::IsModelOverridePresent()) {
     return;
+  }
 
   if (!ShouldFetchModels(off_the_record_,
                          component_updates_enabled_provider_.Run())) {
@@ -418,8 +424,9 @@
 
     auto model_it =
         optimization_target_model_info_map_.find(registration_info.first);
-    if (model_it != optimization_target_model_info_map_.end())
+    if (model_it != optimization_target_model_info_map_.end()) {
       model_info.set_version(model_it->second.get()->GetVersion());
+    }
 
     models_info.push_back(model_info);
     if (optimization_guide_logger_->ShouldEnableDebugLogs()) {
@@ -623,8 +630,9 @@
 void PredictionManager::OnModelReady(const base::FilePath& base_model_dir,
                                      const proto::PredictionModel& model) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (switches::IsModelOverridePresent())
+  if (switches::IsModelOverridePresent()) {
     return;
+  }
 
   DCHECK(model.model_info().has_version() &&
          model.model_info().has_optimization_target());
@@ -812,8 +820,9 @@
   }
   bool success = ProcessAndStoreLoadedModel(*model);
   DCHECK_EQ(optimization_target, model->model_info().optimization_target());
-  if (record_availability_metrics)
+  if (record_availability_metrics) {
     RecordModelAvailableAtRegistration(optimization_target, success);
+  }
   OnProcessLoadedModel(*model, success);
 }
 
@@ -848,12 +857,15 @@
 bool PredictionManager::ProcessAndStoreLoadedModel(
     const proto::PredictionModel& model) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!model.model_info().has_optimization_target())
+  if (!model.model_info().has_optimization_target()) {
     return false;
-  if (!model.model_info().has_version())
+  }
+  if (!model.model_info().has_version()) {
     return false;
-  if (!model.has_model())
+  }
+  if (!model.has_model()) {
     return false;
+  }
   if (!model_registration_info_map_.contains(
           model.model_info().optimization_target())) {
     return false;
@@ -891,8 +903,9 @@
 
   auto model_meta_it =
       optimization_target_model_info_map_.find(optimization_target);
-  if (model_meta_it != optimization_target_model_info_map_.end())
+  if (model_meta_it != optimization_target_model_info_map_.end()) {
     return model_meta_it->second->GetVersion() != new_version;
+  }
 
   return true;
 }
diff --git a/components/optimization_guide/core/prediction_model_store.cc b/components/optimization_guide/core/prediction_model_store.cc
index 5f9e9cb..f7eeb1e9 100644
--- a/components/optimization_guide/core/prediction_model_store.cc
+++ b/components/optimization_guide/core/prediction_model_store.cc
@@ -166,6 +166,14 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!base_store_dir.empty());
 
+  if (!background_task_runner_) {
+    // In unit tests, to avoid leaking a task runner between test runs, the task
+    // runner can be reset at the end of each test. In that case, we'll need to
+    // recreate it here.
+    background_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
+        {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
+  }
+
   // Should not be initialized already.
   DCHECK(base_store_dir_.empty());
 
@@ -507,8 +515,7 @@
   DETACH_FROM_SEQUENCE(sequence_checker_);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   base_store_dir_ = base::FilePath();
-  background_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
-      {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
+  background_task_runner_.reset();
 }
 
 }  // namespace optimization_guide
diff --git a/components/policy/resources/templates/policy_definitions/Network/BlockTruncatedCookies.yaml b/components/policy/resources/templates/policy_definitions/Network/BlockTruncatedCookies.yaml
index d4f738b..b6f9b6a 100644
--- a/components/policy/resources/templates/policy_definitions/Network/BlockTruncatedCookies.yaml
+++ b/components/policy/resources/templates/policy_definitions/Network/BlockTruncatedCookies.yaml
@@ -28,8 +28,8 @@
 schema:
   type: boolean
 supported_on:
-- android:118-
-- chrome_os:118-
-- chrome.*:118-
+- android:118-126
+- chrome_os:118-126
+- chrome.*:118-126
 tags: []
 type: main
diff --git a/components/policy/test/data/pref_mapping/BlockTruncatedCookies.json b/components/policy/test/data/pref_mapping/BlockTruncatedCookies.json
index ec0f2a2..5868d79 100644
--- a/components/policy/test/data/pref_mapping/BlockTruncatedCookies.json
+++ b/components/policy/test/data/pref_mapping/BlockTruncatedCookies.json
@@ -1,22 +1,5 @@
 [
   {
-    "can_be_recommended": true,
-    "os": [
-      "win",
-      "linux",
-      "mac",
-      "chromeos_ash",
-      "chromeos_lacros",
-      "android",
-      "fuchsia"
-    ],
-    "simple_policy_pref_mapping_test": {
-      "pref_name": "profile.cookie_block_truncated",
-      "default_value": true,
-      "values_to_test": [
-        true,
-        false
-      ]
-    }
+    "reason_for_missing_test": "Policy was removed"
   }
 ]
diff --git a/components/privacy_sandbox/android/java/res/xml/tracking_protection_preferences.xml b/components/privacy_sandbox/android/java/res/xml/tracking_protection_preferences.xml
index 51ba2cf..ce7e255a 100644
--- a/components/privacy_sandbox/android/java/res/xml/tracking_protection_preferences.xml
+++ b/components/privacy_sandbox/android/java/res/xml/tracking_protection_preferences.xml
@@ -35,6 +35,20 @@
             app:allowDividerBelow="false" />
 
         <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
+            android:key="ip_protection_toggle"
+            android:title="@string/tracking_protection_ip_protection_toggle_title"
+            android:summary="@string/tracking_protection_ip_protection_toggle_summary"
+            app:allowDividerBelow="false"
+            app:isPreferenceVisible="false" />
+
+        <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
+            android:key="fingerprinting_protection_toggle"
+            android:title="@string/tracking_protection_fingerprinting_protection_toggle_title"
+            android:summary="@string/tracking_protection_fingerprinting_protection_toggle_summary"
+            app:allowDividerBelow="false"
+            app:isPreferenceVisible="false" />
+
+        <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
             android:key="dnt_toggle"
             android:title="@string/tracking_protection_dnt_toggle_title"
             android:summary="@string/tracking_protection_dnt_toggle_summary"
diff --git a/components/privacy_sandbox/android/java/src/org/chromium/components/privacy_sandbox/TrackingProtectionDelegate.java b/components/privacy_sandbox/android/java/src/org/chromium/components/privacy_sandbox/TrackingProtectionDelegate.java
index 4496d6e..67c5ce1 100644
--- a/components/privacy_sandbox/android/java/src/org/chromium/components/privacy_sandbox/TrackingProtectionDelegate.java
+++ b/components/privacy_sandbox/android/java/src/org/chromium/components/privacy_sandbox/TrackingProtectionDelegate.java
@@ -24,6 +24,11 @@
     void setDoNotTrack(boolean enabled);
 
     /**
+     * @return whether the IP protection preference should be shown.
+     */
+    boolean shouldDisplayIpProtection();
+
+    /**
      * @return whether the IP protection is enabled.
      */
     boolean isIpProtectionEnabled();
@@ -32,6 +37,11 @@
     void setIpProtection(boolean enabled);
 
     /**
+     * @return whether the fingerprinting protection preference should be shown.
+     */
+    boolean shouldDisplayFingerprintingProtection();
+
+    /**
      * @return whether the fingerprinting protection is enabled.
      */
     boolean isFingerprintingProtectionEnabled();
diff --git a/components/privacy_sandbox/android/java/src/org/chromium/components/privacy_sandbox/TrackingProtectionSettings.java b/components/privacy_sandbox/android/java/src/org/chromium/components/privacy_sandbox/TrackingProtectionSettings.java
index e00238de..027114e1 100644
--- a/components/privacy_sandbox/android/java/src/org/chromium/components/privacy_sandbox/TrackingProtectionSettings.java
+++ b/components/privacy_sandbox/android/java/src/org/chromium/components/privacy_sandbox/TrackingProtectionSettings.java
@@ -51,6 +51,9 @@
     // Must match keys in tracking_protection_preferences.xml.
     private static final String OFFBOARDING_NOTICE = "offboarding_notice";
     private static final String PREF_BLOCK_ALL_TOGGLE = "block_all_3pcd_toggle";
+    private static final String PREF_IP_PROTECTION_TOGGLE = "ip_protection_toggle";
+    private static final String PREF_FINGERPRINTING_PROTECTION_TOGGLE =
+            "fingerprinting_protection_toggle";
     private static final String PREF_DNT_TOGGLE = "dnt_toggle";
     private static final String PREF_BULLET_TWO = "bullet_point_two";
     private static final String ALLOWED_GROUP = "allowed_group";
@@ -90,6 +93,10 @@
 
         ChromeSwitchPreference blockAll3PCookiesSwitch =
                 (ChromeSwitchPreference) findPreference(PREF_BLOCK_ALL_TOGGLE);
+        ChromeSwitchPreference ipProtectionSwitch =
+                (ChromeSwitchPreference) findPreference(PREF_IP_PROTECTION_TOGGLE);
+        ChromeSwitchPreference fingerprintingProtectionSwitch =
+                (ChromeSwitchPreference) findPreference(PREF_FINGERPRINTING_PROTECTION_TOGGLE);
         ChromeSwitchPreference doNotTrackSwitch =
                 (ChromeSwitchPreference) findPreference(PREF_DNT_TOGGLE);
 
@@ -101,6 +108,29 @@
                     return true;
                 });
 
+        // IP protection switch.
+        if (mDelegate.shouldDisplayIpProtection()) {
+            ipProtectionSwitch.setVisible(true);
+            ipProtectionSwitch.setChecked(mDelegate.isIpProtectionEnabled());
+            ipProtectionSwitch.setOnPreferenceChangeListener(
+                    (preference, newValue) -> {
+                        mDelegate.setIpProtection((boolean) newValue);
+                        return true;
+                    });
+        }
+
+        // Fingerprinting protection switch.
+        if (mDelegate.shouldDisplayFingerprintingProtection()) {
+            fingerprintingProtectionSwitch.setVisible(true);
+            fingerprintingProtectionSwitch.setChecked(
+                    mDelegate.isFingerprintingProtectionEnabled());
+            fingerprintingProtectionSwitch.setOnPreferenceChangeListener(
+                    (preference, newValue) -> {
+                        mDelegate.setFingerprintingProtection((boolean) newValue);
+                        return true;
+                    });
+        }
+
         // Do not track switch.
         doNotTrackSwitch.setChecked(mDelegate.isDoNotTrackEnabled());
         doNotTrackSwitch.setOnPreferenceChangeListener(
diff --git a/components/privacy_sandbox/privacy_sandbox_features.cc b/components/privacy_sandbox/privacy_sandbox_features.cc
index 0b83a83..3f040a8e 100644
--- a/components/privacy_sandbox/privacy_sandbox_features.cc
+++ b/components/privacy_sandbox/privacy_sandbox_features.cc
@@ -230,4 +230,8 @@
     &kPrivacySandboxActivityTypeStorage,
     kPrivacySandboxActivityTypeStorageWithinXDaysName, 60};
 
+BASE_FEATURE(kPrivacySandboxAdsDialogDisabledOnAll3PCBlock,
+             "PrivacySandboxAdsDialogDisabledOnAll3PCBlock",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 }  // namespace privacy_sandbox
diff --git a/components/privacy_sandbox/privacy_sandbox_features.h b/components/privacy_sandbox/privacy_sandbox_features.h
index f61bb4a..b9a841ff 100644
--- a/components/privacy_sandbox/privacy_sandbox_features.h
+++ b/components/privacy_sandbox/privacy_sandbox_features.h
@@ -264,6 +264,10 @@
 extern const base::FeatureParam<int>
     kPrivacySandboxActivityTypeStorageWithinXDays;
 
+// Disables the Privacy Sandbox Ads Dialog when all 3pc are blocked.
+COMPONENT_EXPORT(PRIVACY_SANDBOX_FEATURES)
+BASE_DECLARE_FEATURE(kPrivacySandboxAdsDialogDisabledOnAll3PCBlock);
+
 }  // namespace privacy_sandbox
 
 #endif  // COMPONENTS_PRIVACY_SANDBOX_PRIVACY_SANDBOX_FEATURES_H_
diff --git a/components/privacy_sandbox/privacy_sandbox_test_util.cc b/components/privacy_sandbox/privacy_sandbox_test_util.cc
index 8b3cb80..71a16ecf 100644
--- a/components/privacy_sandbox/privacy_sandbox_test_util.cc
+++ b/components/privacy_sandbox/privacy_sandbox_test_util.cc
@@ -296,6 +296,18 @@
           GetItemValue<std::string>(value), false);
       return;
     }
+    case (StateKey::kBlockAll3pcToggleEnabledUserPrefValue): {
+      SCOPED_TRACE("State Setup: Block all 3pc toggle enabled");
+      testing_pref_service->SetUserPref(prefs::kBlockAll3pcToggleEnabled,
+                                        base::Value(GetItemValue<bool>(value)));
+      return;
+    }
+    case (StateKey::kTrackingProtection3pcdEnabledUserPrefValue): {
+      SCOPED_TRACE("State Setup: Tracking protection 3pcd enabled");
+      testing_pref_service->SetUserPref(prefs::kTrackingProtection3pcdEnabled,
+                                        base::Value(GetItemValue<bool>(value)));
+      return;
+    }
     default:
       NOTREACHED_IN_MIGRATION();
   }
diff --git a/components/privacy_sandbox/privacy_sandbox_test_util.h b/components/privacy_sandbox/privacy_sandbox_test_util.h
index 35b5ecd..8ff901e 100644
--- a/components/privacy_sandbox/privacy_sandbox_test_util.h
+++ b/components/privacy_sandbox/privacy_sandbox_test_util.h
@@ -170,6 +170,8 @@
   kM1RestrictedNoticePreviouslyAcknowledged = 25,
   kAttestationsMap = 26,
   kBlockFledgeJoiningForEtldplus1 = 27,
+  kBlockAll3pcToggleEnabledUserPrefValue = 28,
+  kTrackingProtection3pcdEnabledUserPrefValue = 29,
 };
 
 // Defines the input to the functions under test.
diff --git a/components/privacy_sandbox/privacy_sandbox_test_util_unittest.cc b/components/privacy_sandbox/privacy_sandbox_test_util_unittest.cc
index a8f8193..21d74565 100644
--- a/components/privacy_sandbox/privacy_sandbox_test_util_unittest.cc
+++ b/components/privacy_sandbox/privacy_sandbox_test_util_unittest.cc
@@ -57,6 +57,7 @@
 }  // namespace
 
 // TODO (crbug.com/1408187): Add coverage for all state / input / output keys.
+// TODO (crbug.com/340589498): Parameterize tests in this file.
 class PrivacySandboxTestUtilTest : public testing::Test {
  public:
   PrivacySandboxTestUtilTest()
@@ -182,6 +183,29 @@
   }
 }
 
+TEST_F(PrivacySandboxTestUtilTest,
+       StateKey_VerifykBlockAll3pcToggleEnabledUserPrefValueSetsPref) {
+  std::vector<bool> states = {true, false};
+  for (auto state : states) {
+    ApplyTestState(StateKey::kBlockAll3pcToggleEnabledUserPrefValue, state);
+    EXPECT_EQ(
+        state,
+        prefs()->GetUserPref(prefs::kBlockAll3pcToggleEnabled)->GetBool());
+  }
+}
+
+TEST_F(PrivacySandboxTestUtilTest,
+       StateKey_VerifykTrackingProtection3pcdEnabledUserPrefValueSetsPref) {
+  std::vector<bool> states = {true, false};
+  for (auto state : states) {
+    ApplyTestState(StateKey::kTrackingProtection3pcdEnabledUserPrefValue,
+                   state);
+    EXPECT_EQ(
+        state,
+        prefs()->GetUserPref(prefs::kTrackingProtection3pcdEnabled)->GetBool());
+  }
+}
+
 TEST_F(PrivacySandboxTestUtilTest, StateKey_SiteDataUserDefault) {
   std::vector<ContentSetting> states = {CONTENT_SETTING_ALLOW,
                                         CONTENT_SETTING_BLOCK,
diff --git a/components/privacy_sandbox_strings.grd b/components/privacy_sandbox_strings.grd
index 71993495..6edbd464 100644
--- a/components/privacy_sandbox_strings.grd
+++ b/components/privacy_sandbox_strings.grd
@@ -322,6 +322,18 @@
       <message name="IDS_PRIVACY_SANDBOX_TRACKING_PROTECTION_SNACKBAR_ACTION" desc="Action of the Tracking Protection snackbar." formatter_data="android_java" translateable="false">
         Change protections
       </message>
+      <message name="IDS_TRACKING_PROTECTION_IP_PROTECTION_TOGGLE_TITLE" desc="" formatter_data="android_java" translateable="false">
+        Hide your IP address
+      </message>
+      <message name="IDS_TRACKING_PROTECTION_IP_PROTECTION_TOGGLE_SUMMARY" desc="" formatter_data="android_java" translateable="false">
+        When you’re signed in to Chrome, this setting can limit what suspected trackers can see as you browse. When a page loads, some requests for content get sent through privacy servers.
+      </message>
+      <message name="IDS_TRACKING_PROTECTION_FINGERPRINTING_PROTECTION_TOGGLE_TITLE" desc="" formatter_data="android_java" translateable="false">
+        Limit digital fingerprinting
+      </message>
+      <message name="IDS_TRACKING_PROTECTION_FINGERPRINTING_PROTECTION_TOGGLE_SUMMARY" desc="" formatter_data="android_java" translateable="false">
+        Prevent sites from collecting settings from your browser and computer to create a unique digital fingerprint which can be used to track you.
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/components/user_education/common/feature_promo_specification.cc b/components/user_education/common/feature_promo_specification.cc
index 3c76c90c..ad4a400 100644
--- a/components/user_education/common/feature_promo_specification.cc
+++ b/components/user_education/common/feature_promo_specification.cc
@@ -55,6 +55,7 @@
   // Add the text names of allowlisted keyed notices here:
   static const char* const kAllowedPromoNames[] = {
       "IPH_DesktopPWAsLinkCapturingLaunch",
+      "IPH_ExplicitBrowserSigninPreferenceRemembered",
       "IPH_SignoutWebIntercept",
   };
   for (const auto* promo_name : kAllowedPromoNames) {
diff --git a/components/vector_icons/BUILD.gn b/components/vector_icons/BUILD.gn
index 1d5ee89a..d776e5d0 100644
--- a/components/vector_icons/BUILD.gn
+++ b/components/vector_icons/BUILD.gn
@@ -96,9 +96,7 @@
     "folder_chrome_refresh.icon",
     "folder_managed.icon",
     "folder_managed_refresh.icon",
-    "folder_managed_touch.icon",
     "folder_open.icon",
-    "folder_touch.icon",
     "font_download.icon",
     "font_download_chrome_refresh.icon",
     "font_download_off_chrome_refresh.icon",
diff --git a/components/vector_icons/folder_managed_touch.icon b/components/vector_icons/folder_managed_touch.icon
deleted file mode 100644
index 6125a621..0000000
--- a/components/vector_icons/folder_managed_touch.icon
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 24,
-MOVE_TO, 14, 15,
-R_V_LINE_TO, -1,
-R_H_LINE_TO, -1,
-R_V_LINE_TO, -1,
-R_H_LINE_TO, 2,
-R_V_LINE_TO, 3,
-R_H_LINE_TO, -2,
-R_V_LINE_TO, -1,
-R_H_LINE_TO, 1,
-CLOSE,
-MOVE_TO, 10, 4,
-R_LINE_TO, 2, 2,
-R_H_LINE_TO, 8,
-R_CUBIC_TO, 1.1f, 0, 2, 0.9f, 2, 2,
-R_V_LINE_TO, 10,
-R_CUBIC_TO, 0, 1.1f, -0.9f, 2, -2, 2,
-H_LINE_TO, 4,
-R_CUBIC_TO, -1.1f, 0, -2, -0.9f, -2, -2,
-R_LINE_TO, 0.01f, -12,
-R_CUBIC_TO, 0, -1.1f, 0.89f, -2, 1.99f, -2,
-R_H_LINE_TO, 6,
-CLOSE,
-R_MOVE_TO, 3, 6,
-H_LINE_TO, 8,
-R_V_LINE_TO, 7,
-R_H_LINE_TO, 8,
-R_V_LINE_TO, -5,
-R_H_LINE_TO, -3,
-R_V_LINE_TO, -2,
-CLOSE,
-R_MOVE_TO, -4, 1,
-R_H_LINE_TO, 1,
-R_V_LINE_TO, 1,
-H_LINE_TO, 9,
-R_V_LINE_TO, -1,
-CLOSE,
-R_MOVE_TO, 2, 0,
-R_H_LINE_TO, 1,
-R_V_LINE_TO, 1,
-R_H_LINE_TO, -1,
-R_V_LINE_TO, -1,
-CLOSE,
-R_MOVE_TO, -2, 2,
-R_H_LINE_TO, 1,
-R_V_LINE_TO, 1,
-H_LINE_TO, 9,
-R_V_LINE_TO, -1,
-CLOSE,
-R_MOVE_TO, 2, 0,
-R_H_LINE_TO, 1,
-R_V_LINE_TO, 1,
-R_H_LINE_TO, -1,
-R_V_LINE_TO, -1,
-CLOSE,
-R_MOVE_TO, -2, 2,
-R_H_LINE_TO, 1,
-R_V_LINE_TO, 1,
-H_LINE_TO, 9,
-R_V_LINE_TO, -1,
-CLOSE,
-R_MOVE_TO, 2, 0,
-R_H_LINE_TO, 1,
-R_V_LINE_TO, 1,
-R_H_LINE_TO, -1,
-R_V_LINE_TO, -1,
-CLOSE
diff --git a/components/vector_icons/folder_touch.icon b/components/vector_icons/folder_touch.icon
deleted file mode 100644
index 7d4e6dfd..0000000
--- a/components/vector_icons/folder_touch.icon
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-CANVAS_DIMENSIONS, 24,
-MOVE_TO, 10, 4,
-H_LINE_TO, 4,
-R_CUBIC_TO, -1.1f, 0, -1.99f, 0.9f, -1.99f, 2,
-LINE_TO, 2, 18,
-R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
-R_H_LINE_TO, 16,
-R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
-V_LINE_TO, 8,
-R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
-R_H_LINE_TO, -8,
-R_LINE_TO, -2, -2,
-CLOSE
diff --git a/components/visited_url_ranking/internal/history_url_visit_data_fetcher.cc b/components/visited_url_ranking/internal/history_url_visit_data_fetcher.cc
index 7638214..3dfb91e 100644
--- a/components/visited_url_ranking/internal/history_url_visit_data_fetcher.cc
+++ b/components/visited_url_ranking/internal/history_url_visit_data_fetcher.cc
@@ -8,7 +8,6 @@
 #include <utility>
 
 #include "base/containers/contains.h"
-#include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_types.h"
@@ -23,7 +22,7 @@
 using URLVisitVariant = URLVisitAggregate::URLVisitVariant;
 
 HistoryURLVisitDataFetcher::HistoryURLVisitDataFetcher(
-    base::WeakPtr<history::HistoryService> history_service)
+    history::HistoryService* history_service)
     : history_service_(history_service) {}
 
 HistoryURLVisitDataFetcher::~HistoryURLVisitDataFetcher() = default;
@@ -77,8 +76,14 @@
       history.visit_count += 1;
       history.total_foreground_duration +=
           annotated_visit.context_annotations.total_foreground_duration;
-      // TODO(crbug.com/330580109): Add `in_cluster`, dismiss/done `status`
-      // signals.
+
+      if (!history.last_app_id.has_value() &&
+          annotated_visit.visit_row.app_id.has_value()) {
+        history.last_app_id = annotated_visit.visit_row.app_id;
+      }
+
+      // TODO(crbug.com/340885723): Wire `in_cluster` signal.
+      // TODO(crbug.com/340887237): Wire `interaction_state` signal.
     }
   }
 
diff --git a/components/visited_url_ranking/internal/history_url_visit_data_fetcher.h b/components/visited_url_ranking/internal/history_url_visit_data_fetcher.h
index 85492b4..194a8b7e36 100644
--- a/components/visited_url_ranking/internal/history_url_visit_data_fetcher.h
+++ b/components/visited_url_ranking/internal/history_url_visit_data_fetcher.h
@@ -8,7 +8,7 @@
 #include <utility>
 #include <vector>
 
-#include "base/memory/weak_ptr.h"
+#include "base/memory/raw_ptr.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "components/visited_url_ranking/public/fetch_result.h"
 #include "components/visited_url_ranking/public/url_visit.h"
@@ -24,8 +24,7 @@
 // Fetches URL visit data from the history service.
 class HistoryURLVisitDataFetcher : public URLVisitDataFetcher {
  public:
-  explicit HistoryURLVisitDataFetcher(
-      base::WeakPtr<history::HistoryService> history_service);
+  explicit HistoryURLVisitDataFetcher(history::HistoryService* history_service);
   HistoryURLVisitDataFetcher(const HistoryURLVisitDataFetcher&) = delete;
   ~HistoryURLVisitDataFetcher() override;
 
@@ -40,7 +39,7 @@
       FetchOptions::FetchSources requested_fetch_sources,
       std::vector<history::AnnotatedVisit> annotated_visits);
 
-  const base::WeakPtr<history::HistoryService> history_service_;
+  const raw_ptr<history::HistoryService> history_service_;
 
   // The task tracker for the HistoryService callbacks.
   base::CancelableTaskTracker task_tracker_;
diff --git a/components/visited_url_ranking/internal/history_url_visit_data_fetcher_unittest.cc b/components/visited_url_ranking/internal/history_url_visit_data_fetcher_unittest.cc
index eac05996..75eff16 100644
--- a/components/visited_url_ranking/internal/history_url_visit_data_fetcher_unittest.cc
+++ b/components/visited_url_ranking/internal/history_url_visit_data_fetcher_unittest.cc
@@ -29,6 +29,7 @@
     const GURL& url,
     float visibility_score,
     const std::string& originator_cache_guid,
+    const std::optional<std::string> app_id = std::nullopt,
     const base::Time visit_time = base::Time::Now()) {
   history::VisitContentModelAnnotations model_annotations;
   model_annotations.visibility_score = visibility_score;
@@ -41,6 +42,7 @@
   visit_row.visit_time = visit_time;
   visit_row.is_known_to_sync = true;
   visit_row.originator_cache_guid = originator_cache_guid;
+  visit_row.app_id = app_id;
 
   history::AnnotatedVisit annotated_visit;
   annotated_visit.url_row = std::move(url_row);
@@ -89,7 +91,8 @@
                 -> base::CancelableTaskTracker::TaskId {
               std::vector<history::AnnotatedVisit> annotated_visits = {};
               annotated_visits.emplace_back(SampleAnnotatedVisit(
-                  1, GURL(base::StrCat({kSampleSearchUrl, "1"})), 1.0f, ""));
+                  1, GURL(base::StrCat({kSampleSearchUrl, "1"})), 1.0f, "",
+                  "sample_app_id"));
               annotated_visits.emplace_back(SampleAnnotatedVisit(
                   2, GURL(base::StrCat({kSampleSearchUrl, "2"})), 0.75f,
                   "foreign_session_guid"));
@@ -97,7 +100,7 @@
               return 0;
             }));
     history_url_visit_fetcher_ = std::make_unique<HistoryURLVisitDataFetcher>(
-        mock_history_service_->AsWeakPtr());
+        mock_history_service_.get());
   }
 
   FetchResult FetchAndGetResult(const FetchOptions& options) {
@@ -129,6 +132,11 @@
   auto result = FetchAndGetResult(options);
   EXPECT_EQ(result.status, FetchResult::Status::kSuccess);
   EXPECT_EQ(result.data.size(), 2u);
+
+  const auto entry_url = GURL(base::StrCat({kSampleSearchUrl, "1"}));
+  const auto* history = std::get_if<URLVisitAggregate::HistoryData>(
+      &result.data.at(entry_url.spec()));
+  EXPECT_EQ(history->last_app_id, "sample_app_id");
 }
 
 INSTANTIATE_TEST_SUITE_P(All,
diff --git a/components/visited_url_ranking/internal/visited_url_ranking_service_impl.cc b/components/visited_url_ranking/internal/visited_url_ranking_service_impl.cc
index 358911d..9a91d6e6 100644
--- a/components/visited_url_ranking/internal/visited_url_ranking_service_impl.cc
+++ b/components/visited_url_ranking/internal/visited_url_ranking_service_impl.cc
@@ -4,6 +4,7 @@
 
 #include "components/visited_url_ranking/internal/visited_url_ranking_service_impl.h"
 
+#include <algorithm>
 #include <map>
 #include <memory>
 #include <queue>
@@ -25,6 +26,26 @@
 
 namespace visited_url_ranking {
 
+namespace {
+
+base::Time GetVisitTime(const URLVisitAggregate::URLVisitVariant& visit) {
+  const URLVisitAggregate::TabData* tab_data =
+      std::get_if<URLVisitAggregate::TabData>(&visit);
+  if (tab_data) {
+    return tab_data->last_active;
+  }
+
+  const URLVisitAggregate::HistoryData* history_data =
+      std::get_if<URLVisitAggregate::HistoryData>(&visit);
+  if (history_data) {
+    return history_data->last_visited.visit_row.visit_time;
+  }
+
+  return base::Time::Max();
+}
+
+}  // namespace
+
 // Combines `URLVisitVariant` data obtained from various fetchers into
 // `URLVisitAggregate` objects. Leverages the `URLMergeKey` in order to
 // reconcile what data belongs to the same aggregate object.
@@ -98,6 +119,33 @@
     std::vector<URLVisitAggregate> visits,
     RankVisitAggregatesCallback callback) {
   // TODO(crbug.com/330577142): Implement `URLVisitAggregate` ranking logic.
+
+  // To enable development and testing, below we implement a stub implementation
+  // that simply sorts |visits| by minimal |last_active|.
+  size_t num_visits = visits.size();
+
+  // Extract sort keys (|fetcher_data_map| may have any size) and create sort
+  // permutation, which also avoids object movement churn from direct sort.
+  std::vector<std::pair<base::Time, size_t>> keys;
+  keys.reserve(num_visits);
+  for (size_t i = 0; i < num_visits; ++i) {
+    const auto& aggregate = visits[i];
+    base::Time min_last_visit_time = base::Time::Max();
+    for (auto& key_value : aggregate.fetcher_data_map) {
+      min_last_visit_time =
+          std::min(min_last_visit_time, GetVisitTime(key_value.second));
+    }
+    keys.emplace_back(min_last_visit_time, i);
+  }
+  // Sort from oldest to newest.
+  std::sort(keys.begin(), keys.end());
+
+  // Apply permutation, and reverse, so newest comes first.
+  std::vector<URLVisitAggregate> ranked_visits(num_visits);
+  for (size_t i = 0; i < num_visits; ++i) {
+    ranked_visits[num_visits - 1 - i] = std::move(visits[keys[i].second]);
+  }
+  std::move(callback).Run(ResultStatus::kSuccess, std::move(ranked_visits));
 }
 
 void VisitedURLRankingServiceImpl::MergeVisitsAndCallback(
diff --git a/components/visited_url_ranking/public/url_visit.cc b/components/visited_url_ranking/public/url_visit.cc
index 57eba738..dfc62fbf 100644
--- a/components/visited_url_ranking/public/url_visit.cc
+++ b/components/visited_url_ranking/public/url_visit.cc
@@ -5,6 +5,7 @@
 #include "components/visited_url_ranking/public/url_visit.h"
 
 #include <set>
+#include <string>
 #include <utility>
 #include <variant>
 
@@ -76,8 +77,17 @@
   visit_count = 1;
   total_foreground_duration =
       last_visited.context_annotations.total_foreground_duration;
+  if (last_visited.visit_row.app_id.has_value()) {
+    last_app_id = last_visited.visit_row.app_id;
+  }
 }
 
+URLVisitAggregate::HistoryData::HistoryData(
+    URLVisitAggregate::HistoryData&& other) = default;
+
+URLVisitAggregate::HistoryData& URLVisitAggregate::HistoryData::operator=(
+    URLVisitAggregate::HistoryData&& other) = default;
+
 URLVisitAggregate::HistoryData::~HistoryData() = default;
 
 }  // namespace visited_url_ranking
diff --git a/components/visited_url_ranking/public/url_visit.h b/components/visited_url_ranking/public/url_visit.h
index 8581be3..efe10d4 100644
--- a/components/visited_url_ranking/public/url_visit.h
+++ b/components/visited_url_ranking/public/url_visit.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <optional>
 #include <set>
+#include <string>
 #include <variant>
 #include <vector>
 
@@ -102,12 +103,19 @@
 
   struct HistoryData {
     explicit HistoryData(history::AnnotatedVisit annotated_visit);
+    HistoryData(const HistoryData&) = delete;
+    HistoryData(HistoryData&& other);
+    HistoryData& operator=(HistoryData&& other);
     ~HistoryData();
 
     // The last annotated visit associated with the given URL visit in a given
     // time period.
     history::AnnotatedVisit last_visited;
 
+    // The last `app_id` value if any for any of the visits associated with the
+    // URL visit aggregate.
+    std::optional<std::string> last_app_id = std::nullopt;
+
     // Whether any of the annotated visits for the given URL visit aggregate are
     // part of a cluster.
     bool in_cluster = false;
diff --git a/components/viz/common/viz_utils.cc b/components/viz/common/viz_utils.cc
index 6eb0b442..e27c2a2 100644
--- a/components/viz/common/viz_utils.cc
+++ b/components/viz/common/viz_utils.cc
@@ -11,6 +11,7 @@
 #include "base/system/sys_info.h"
 #include "build/build_config.h"
 #include "cc/base/math_util.h"
+#include "components/viz/common/frame_sinks/copy_output_request.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/rrect_f.h"
@@ -213,4 +214,34 @@
   return false;
 }
 
+void SetCopyOutoutRequestResultSize(CopyOutputRequest* request,
+                                    const gfx::Rect& src_rect,
+                                    const gfx::Size& output_size,
+                                    const gfx::Size& surface_size_in_pixels) {
+  CHECK(request);
+  if (!src_rect.IsEmpty()) {
+    request->set_area(src_rect);
+  }
+  if (output_size.IsEmpty()) {
+    return;
+  }
+  // The CopyOutputRequest API does not allow fixing the output size. Instead
+  // we have the set area and scale in such a way that it would result in the
+  // desired output size.
+  if (!request->has_area()) {
+    request->set_area(gfx::Rect(surface_size_in_pixels));
+  }
+  request->set_result_selection(gfx::Rect(output_size));
+  const gfx::Rect& area = request->area();
+  // Viz would normally return an empty result for an empty area.
+  // However, this guard here is still necessary to protect against setting
+  // an illegal scaling ratio.
+  if (area.IsEmpty()) {
+    return;
+  }
+  request->SetScaleRatio(
+      gfx::Vector2d(area.width(), area.height()),
+      gfx::Vector2d(output_size.width(), output_size.height()));
+}
+
 }  // namespace viz
diff --git a/components/viz/common/viz_utils.h b/components/viz/common/viz_utils.h
index 2d4865d..1492902 100644
--- a/components/viz/common/viz_utils.h
+++ b/components/viz/common/viz_utils.h
@@ -25,6 +25,8 @@
 VIZ_COMMON_EXPORT bool AlwaysUseWideColorGamut();
 #endif
 
+class CopyOutputRequest;
+
 // This takes a gfx::Rect and a clip region quad in the same space,
 // and returns a quad with the same proportions in the space -0.5->0.5.
 VIZ_COMMON_EXPORT bool GetScaledRegion(const gfx::Rect& rect,
@@ -71,6 +73,13 @@
     const DrawQuad* quad,
     const gfx::RectF& target_quad);
 
+// Customizes the output sizes of a `CopyOutputRequest`.
+VIZ_COMMON_EXPORT void SetCopyOutoutRequestResultSize(
+    CopyOutputRequest* request,
+    const gfx::Rect& src_rect,
+    const gfx::Size& output_size,
+    const gfx::Size& surface_size_in_pixels);
+
 }  // namespace viz
 
 #endif  // COMPONENTS_VIZ_COMMON_VIZ_UTILS_H_
diff --git a/components/viz/host/host_frame_sink_manager.cc b/components/viz/host/host_frame_sink_manager.cc
index f437063..7b13bd2 100644
--- a/components/viz/host/host_frame_sink_manager.cc
+++ b/components/viz/host/host_frame_sink_manager.cc
@@ -312,7 +312,9 @@
     const blink::SameDocNavigationScreenshotDestinationToken&
         destination_token) {
   auto it = screenshot_destinations_.find(destination_token);
-  CHECK(it != screenshot_destinations_.end());
+  if (it == screenshot_destinations_.end()) {
+    return;
+  }
   screenshot_destinations_.erase(it);
 }
 
@@ -445,11 +447,10 @@
   if (it == screenshot_destinations_.end()) {
     return;
   }
-  SkBitmap immutable =
-      copy_output_result->ScopedAccessSkBitmap().GetOutScopedBitmap();
-  immutable.setImmutable();
-  std::move(it->second).Run(destination_token, std::move(immutable));
+  auto callback = std::move(it->second);
   screenshot_destinations_.erase(it);
+  std::move(callback).Run(
+      copy_output_result->ScopedAccessSkBitmap().GetOutScopedBitmap());
 }
 
 uint32_t HostFrameSinkManager::CacheBackBufferForRootSink(
@@ -518,6 +519,12 @@
   return has_resources;
 }
 
+void HostFrameSinkManager::SetSameDocNavigationScreenshotSizeForTesting(
+    const gfx::Size& result_size) {
+  frame_sink_manager_->SetSameDocNavigationScreenshotSizeForTesting(  // IN-TEST
+      result_size);
+}
+
 HostFrameSinkManager::FrameSinkData::FrameSinkData() = default;
 
 HostFrameSinkManager::FrameSinkData::FrameSinkData(FrameSinkData&& other) =
diff --git a/components/viz/host/host_frame_sink_manager.h b/components/viz/host/host_frame_sink_manager.h
index 790ef5a7..2253c00 100644
--- a/components/viz/host/host_frame_sink_manager.h
+++ b/components/viz/host/host_frame_sink_manager.h
@@ -201,10 +201,8 @@
                            std::unique_ptr<CopyOutputRequest> request,
                            bool capture_exact_surface_id = false);
 
-  using ScreenshotDestinationReadyCallback = base::OnceCallback<void(
-      const blink::SameDocNavigationScreenshotDestinationToken&
-          destination_token,
-      SkBitmap copy_output)>;
+  using ScreenshotDestinationReadyCallback =
+      base::OnceCallback<void(const SkBitmap& copy_output)>;
   // Sets the callback which is invoked when a `CopyOutputResult` associated
   // with `destination_token` is received by the host/browser process from the
   // Viz process. Must be called once per `destination_token`.
@@ -261,6 +259,9 @@
       const blink::ViewTransitionToken& transition_token);
   bool HasUnclaimedViewTransitionResourcesForTest();
 
+  void SetSameDocNavigationScreenshotSizeForTesting(
+      const gfx::Size& result_size);
+
   const DebugRendererSettings& debug_renderer_settings() const {
     return debug_renderer_settings_;
   }
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 49614db8..ef64adc 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -436,22 +436,26 @@
     if (enabled) {
       DBG_LOG("renderer.skia.render_pass_backings",
               "render_pass_backings_ = [");
-      for (auto& kv : render_pass_backings) {
+      for (auto& [render_pass_id, backing] : render_pass_backings) {
         base::trace_event::TracedValueJSON value;
         base::trace_event::TracedValue::Dictionary(
             {
-                {"size", kv.second.size.ToString()},
-                {"generate_mipmap", kv.second.generate_mipmap},
-                {"color_space", kv.second.color_space.ToString()},
-                {"format", kv.second.format.ToString()},
-                {"mailbox", kv.second.mailbox.ToDebugString()},
-                {"is_root", kv.second.is_root},
-                {"is_scanout", kv.second.is_scanout},
-                {"scanout_dcomp_surface", kv.second.scanout_dcomp_surface},
+                {"size", backing.size.ToString()},
+                {"generate_mipmap", backing.generate_mipmap},
+                {"color_space", backing.color_space.ToString()},
+                {"alpha_type",
+                 backing.alpha_type == RenderPassAlphaType::kPremul ? "premul"
+                                                                    : "opaque"},
+                {"format", backing.format.ToString()},
+                {"mailbox", backing.mailbox.ToDebugString()},
+                {"is_root", backing.is_root},
+                {"is_scanout", backing.is_scanout},
+                {"scanout_dcomp_surface", backing.scanout_dcomp_surface},
+                {"drawn_rect", backing.drawn_rect.ToString()},
             })
             .WriteToValue(&value);
         DBG_LOG("renderer.skia.render_pass_backings", "%" PRIu64 ": %s",
-                kv.first.value(), value.ToFormattedJSON().c_str());
+                render_pass_id.value(), value.ToFormattedJSON().c_str());
       }
       DBG_LOG("renderer.skia.render_pass_backings", "]");
     }
@@ -2999,7 +3003,7 @@
       if (auto backing = GetRenderPassBackingForDirectScanout(
               overlay.rpdq->render_pass_id);
           backing) {
-        DBG_LOG("delegated.overlays.log",
+        DBG_LOG("delegated.overlay.log",
                 "Pass %" PRIu64 ": RPDQ overlay can scanout directly",
                 overlay.rpdq->render_pass_id.value());
 
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 1be038db..5549f14 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -30,6 +30,7 @@
 #include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/surfaces/surface_info.h"
 #include "components/viz/common/surfaces/video_capture_target.h"
+#include "components/viz/common/viz_utils.h"
 #include "components/viz/service/display/display.h"
 #include "components/viz/service/display/shared_bitmap_manager.h"
 #include "components/viz/service/frame_sinks/frame_counter.h"
@@ -924,6 +925,15 @@
               &RemoveSurfaceReferenceAndDispatchCopyOutputRequestCallback,
               frame_sink_manager_->GetWeakPtr(), surface_info.id(),
               frame.metadata.screenshot_destination.value()));
+      if (const auto& size_for_testing =
+              frame_sink_manager_
+                  ->copy_output_request_result_size_for_testing();  // IN-TEST
+          UNLIKELY(!size_for_testing.IsEmpty())) {
+        SetCopyOutoutRequestResultSize(copy_request.get(), gfx::Rect(),
+                                       size_for_testing,
+                                       prev_surface->size_in_pixels());
+      }
+
       copy_request->set_result_task_runner(
           base::SequencedTaskRunner::GetCurrentDefault());
 
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
index 14607a0f..22f171d 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
@@ -948,4 +948,11 @@
   std::move(callback).Run(!transition_token_to_animation_manager_.empty());
 }
 
+void FrameSinkManagerImpl::SetSameDocNavigationScreenshotSizeForTesting(
+    const gfx::Size& result_size,
+    SetSameDocNavigationScreenshotSizeForTestingCallback callback) {
+  copy_output_request_result_size_for_testing_ = result_size;
+  std::move(callback).Run();
+}
+
 }  // namespace viz
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.h b/components/viz/service/frame_sinks/frame_sink_manager_impl.h
index c28c842..460f0c6 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.h
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.h
@@ -169,6 +169,9 @@
       const blink::ViewTransitionToken& transition_token) override;
   void HasUnclaimedViewTransitionResourcesForTest(
       HasUnclaimedViewTransitionResourcesForTestCallback callback) override;
+  void SetSameDocNavigationScreenshotSizeForTesting(
+      const gfx::Size& result_size,
+      SetSameDocNavigationScreenshotSizeForTestingCallback callback) override;
 
   void DestroyFrameSinkBundle(const FrameSinkBundleId& id);
 
@@ -310,6 +313,10 @@
     return &reserved_resource_id_tracker_;
   }
 
+  const gfx::Size& copy_output_request_result_size_for_testing() const {
+    return copy_output_request_result_size_for_testing_;
+  }
+
  private:
   friend class FrameSinkManagerTest;
   friend class CompositorFrameSinkSupportTest;
@@ -501,6 +508,8 @@
 
   ReservedResourceIdTracker reserved_resource_id_tracker_;
 
+  gfx::Size copy_output_request_result_size_for_testing_;
+
   base::WeakPtrFactory<FrameSinkManagerImpl> weak_factory_{this};
 };
 
diff --git a/components/viz/service/surfaces/surface_manager.h b/components/viz/service/surfaces/surface_manager.h
index ffe4eef9..b0c56e8 100644
--- a/components/viz/service/surfaces/surface_manager.h
+++ b/components/viz/service/surfaces/surface_manager.h
@@ -135,15 +135,6 @@
   // possibly because a renderer process has crashed.
   void InvalidateFrameSinkId(const FrameSinkId& frame_sink_id);
 
-  // Register a relationship between two namespaces.  This relationship means
-  // that surfaces from the child namespace will be displayed in the parent.
-  // Children are allowed to use any begin frame source that their parent can
-  // use.
-  void RegisterFrameSinkHierarchy(const FrameSinkId& parent_frame_sink_id,
-                                  const FrameSinkId& child_frame_sink_id);
-  void UnregisterFrameSinkHierarchy(const FrameSinkId& parent_frame_sink_id,
-                                    const FrameSinkId& child_frame_sink_id);
-
   // Returns the top level root SurfaceId. Surfaces that are not reachable
   // from the top level root may be garbage collected. It will not be a valid
   // SurfaceId and will never correspond to a surface.
diff --git a/components/viz/test/test_frame_sink_manager.h b/components/viz/test/test_frame_sink_manager.h
index 2d1ace3..3668f36 100644
--- a/components/viz/test/test_frame_sink_manager.h
+++ b/components/viz/test/test_frame_sink_manager.h
@@ -84,6 +84,9 @@
       const blink::ViewTransitionToken& transition_token) override {}
   void HasUnclaimedViewTransitionResourcesForTest(
       HasUnclaimedViewTransitionResourcesForTestCallback callback) override {}
+  void SetSameDocNavigationScreenshotSizeForTesting(
+      const gfx::Size& result_size,
+      SetSameDocNavigationScreenshotSizeForTestingCallback callback) override {}
 
   mojo::Receiver<mojom::FrameSinkManager> receiver_{this};
   mojo::Remote<mojom::FrameSinkManagerClient> client_;
diff --git a/content/browser/DEPS b/content/browser/DEPS
index b8b20d6..2fc0a9f 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -136,7 +136,7 @@
     "+components/os_crypt/async",
     "+services/network/test",
   ],
-  ".*test_utils\.(h|cc)": [
+  ".*test_utils?(_.+)?\.(h|cc)": [
     "+services/network/test",
   ],
   "browser_interface_binders\.cc": [
diff --git a/content/browser/attribution_reporting/attribution_internals_browsertest.cc b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
index fefd62d..55d238c 100644
--- a/content/browser/attribution_reporting/attribution_internals_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
@@ -863,6 +863,7 @@
                                            SourceBuilder(now).BuildStored())
                                  .SetReportTime(now)
                                  .SetPriority(7)
+                                 .SetReportId(AttributionReport::Id(1))
                                  .Build();
 
   std::vector<AttributionReport> stored_reports;
@@ -875,10 +876,10 @@
                   callback) { std::move(callback).Run(stored_reports); });
 
   report.set_report_time(report.report_time() + base::Hours(1));
-  manager()->NotifyReportSent(report,
-                              /*is_debug_report=*/false,
-                              SendResult(SendResult::Status::kSent, net::OK,
-                                         /*http_response_code=*/200));
+
+  // Give the report a distinct ID to ensure that it won't overwrite the UI row
+  // for the stored report.
+  report.set_id(AttributionReport::Id(2));
 
   EXPECT_CALL(*manager(), ClearData)
       .WillOnce([&](base::Time delete_begin, base::Time delete_end,
@@ -918,6 +919,10 @@
   // Wait for the table to rendered.
   TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
   ClickRefreshButton();
+  manager()->NotifyReportSent(report,
+                              /*is_debug_report=*/false,
+                              SendResult(SendResult::Status::kSent, net::OK,
+                                         /*http_response_code=*/200));
   ASSERT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
 
   // Click the clear storage button and expect that the report table is emptied.
diff --git a/content/browser/browsing_data/browsing_data_filter_builder_impl.cc b/content/browser/browsing_data/browsing_data_filter_builder_impl.cc
index cd3bd471..04a97bf9 100644
--- a/content/browser/browsing_data/browsing_data_filter_builder_impl.cc
+++ b/content/browser/browsing_data/browsing_data_filter_builder_impl.cc
@@ -219,9 +219,13 @@
 }
 
 bool BrowsingDataFilterBuilderImpl::MatchesAllOriginsAndDomains() {
-  return mode_ == Mode::kPreserve && origins_.empty() && domains_.empty() &&
-         !partitioned_cookies_only_ &&
-         cookie_partition_key_collection_.ContainsAllKeys() && !HasStorageKey();
+  return MatchesMostOriginsAndDomains() && origins_.empty() &&
+         domains_.empty() && cookie_partition_key_collection_.ContainsAllKeys();
+}
+
+bool BrowsingDataFilterBuilderImpl::MatchesMostOriginsAndDomains() {
+  return mode_ == Mode::kPreserve && !partitioned_cookies_only_ &&
+         !HasStorageKey();
 }
 
 bool BrowsingDataFilterBuilderImpl::MatchesNothing() {
diff --git a/content/browser/browsing_data/browsing_data_filter_builder_impl.h b/content/browser/browsing_data/browsing_data_filter_builder_impl.h
index 43a7a61..8e82a319 100644
--- a/content/browser/browsing_data/browsing_data_filter_builder_impl.h
+++ b/content/browser/browsing_data/browsing_data_filter_builder_impl.h
@@ -42,6 +42,7 @@
   bool MatchesWithSavedStorageKey(
       const blink::StorageKey& other_key) const override;
   bool MatchesAllOriginsAndDomains() override;
+  bool MatchesMostOriginsAndDomains() override;
   bool MatchesNothing() override;
   void SetPartitionedCookiesOnly(bool value) override;
   bool PartitionedCookiesOnly() const override;
diff --git a/content/browser/browsing_data/browsing_data_filter_builder_impl_unittest.cc b/content/browser/browsing_data/browsing_data_filter_builder_impl_unittest.cc
index 79faae5..2a2dfeaf 100644
--- a/content/browser/browsing_data/browsing_data_filter_builder_impl_unittest.cc
+++ b/content/browser/browsing_data/browsing_data_filter_builder_impl_unittest.cc
@@ -1155,4 +1155,62 @@
   EXPECT_EQ(builder, *builder.Copy());
 }
 
+TEST(BrowsingDataFilterBuilderImplTest, DeleteModeDoesntMatchMost) {
+  BrowsingDataFilterBuilderImpl builder(
+      BrowsingDataFilterBuilder::Mode::kDelete);
+
+  EXPECT_FALSE(builder.MatchesAllOriginsAndDomains());
+  EXPECT_FALSE(builder.MatchesMostOriginsAndDomains());
+}
+
+TEST(BrowsingDataFilterBuilderImplTest, PreserveModeMatchesAll) {
+  BrowsingDataFilterBuilderImpl builder(
+      BrowsingDataFilterBuilder::Mode::kPreserve);
+
+  EXPECT_TRUE(builder.MatchesAllOriginsAndDomains());
+  EXPECT_TRUE(builder.MatchesMostOriginsAndDomains());
+}
+
+TEST(BrowsingDataFilterBuilderImplTest,
+     PreserveModeWithOriginsOrDomainsMatchesMost) {
+  BrowsingDataFilterBuilderImpl builder(
+      BrowsingDataFilterBuilder::Mode::kPreserve);
+  builder.AddOrigin(url::Origin::Create(GURL("http://example.test")));
+  builder.AddRegisterableDomain("example.test");
+
+  EXPECT_FALSE(builder.MatchesAllOriginsAndDomains());
+  EXPECT_TRUE(builder.MatchesMostOriginsAndDomains());
+}
+
+TEST(BrowsingDataFilterBuilderImplTest,
+     PreserveModeWithCookiePartitionKeysMatchesMost) {
+  BrowsingDataFilterBuilderImpl builder(
+      BrowsingDataFilterBuilder::Mode::kPreserve);
+  builder.SetCookiePartitionKeyCollection(net::CookiePartitionKeyCollection());
+
+  EXPECT_FALSE(builder.MatchesAllOriginsAndDomains());
+  EXPECT_TRUE(builder.MatchesMostOriginsAndDomains());
+}
+
+TEST(BrowsingDataFilterBuilderImplTest,
+     PreserveModeWithStorageKeyDoesntMatchMost) {
+  BrowsingDataFilterBuilderImpl builder(
+      BrowsingDataFilterBuilder::Mode::kPreserve);
+  builder.SetStorageKey(
+      blink::StorageKey::CreateFromStringForTesting("http://example.test"));
+
+  EXPECT_FALSE(builder.MatchesAllOriginsAndDomains());
+  EXPECT_FALSE(builder.MatchesMostOriginsAndDomains());
+}
+
+TEST(BrowsingDataFilterBuilderImplTest,
+     PreserveModePartitionedCookiesOnlyDoesntMatchMost) {
+  BrowsingDataFilterBuilderImpl builder(
+      BrowsingDataFilterBuilder::Mode::kPreserve);
+  builder.SetPartitionedCookiesOnly(true);
+
+  EXPECT_FALSE(builder.MatchesAllOriginsAndDomains());
+  EXPECT_FALSE(builder.MatchesMostOriginsAndDomains());
+}
+
 }  // namespace content
diff --git a/content/browser/device_posture/foldable_apis_origin_trial_browsertest.cc b/content/browser/device_posture/foldable_apis_origin_trial_browsertest.cc
index d46acb6be..e20a9b14 100644
--- a/content/browser/device_posture/foldable_apis_origin_trial_browsertest.cc
+++ b/content/browser/device_posture/foldable_apis_origin_trial_browsertest.cc
@@ -139,8 +139,14 @@
   std::unique_ptr<content::URLLoaderInterceptor> interceptor_;
 };
 
+// TODO(crbug.com/339983706): Fix flaky test on macOS.
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_ValidOriginTrialToken DISABLED_ValidOriginTrialToken
+#else
+#define MAYBE_ValidOriginTrialToken ValidOriginTrialToken
+#endif
 IN_PROC_BROWSER_TEST_F(FoldableAPIsOriginTrialBrowserTest,
-                       ValidOriginTrialToken) {
+                       MAYBE_ValidOriginTrialToken) {
   ASSERT_TRUE(NavigateToURL(shell(), kValidTokenUrl));
   SetUpFoldableState();
   EXPECT_TRUE(HasDevicePostureApi());
diff --git a/content/browser/network/http_cookie_browsertest.cc b/content/browser/network/http_cookie_browsertest.cc
index 18c9ac318..f8117bc 100644
--- a/content/browser/network/http_cookie_browsertest.cc
+++ b/content/browser/network/http_cookie_browsertest.cc
@@ -42,6 +42,7 @@
 
 constexpr char kHostA[] = "a.test";
 constexpr char kHostB[] = "b.test";
+constexpr char kHostC[] = "c.test";
 constexpr char kSameSiteNoneCookieName[] = "samesite_none_cookie";
 constexpr char kSameSiteStrictCookieName[] = "samesite_strict_cookie";
 constexpr char kSameSiteLaxCookieName[] = "samesite_lax_cookie";
@@ -885,9 +886,178 @@
               net::CookieStringIs(IsEmpty()));
 }
 
+class AncestorChainBitEnabledThirdPartyCookiesBlockedTest
+    : public ContentBrowserTest,
+      public ::testing::WithParamInterface<bool> {
+ public:
+  AncestorChainBitEnabledThirdPartyCookiesBlockedTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+    feature_list_.InitWithFeatureStates(
+        {{net::features::kForceThirdPartyCookieBlocking, true},
+         {net::features::kAncestorChainBitEnabledInPartitionedCookies,
+          AncestorChainBitEnabled()}});
+  }
+
+  bool AncestorChainBitEnabled() { return GetParam(); }
+
+  ~AncestorChainBitEnabledThirdPartyCookiesBlockedTest() override = default;
+
+  void SetUpOnMainThread() override {
+    ContentBrowserTest::SetUpOnMainThread();
+    host_resolver()->AddRule("*", "127.0.0.1");
+    https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
+    https_server()->AddDefaultHandlers(GetTestDataFilePath());
+    https_server()->RegisterRequestHandler(
+        base::BindRepeating(&HandleEchoCookiesWithCorsRequest));
+    ASSERT_TRUE(https_server()->Start());
+  }
+
+  WebContents* web_contents() const { return shell()->web_contents(); }
+
+  net::EmbeddedTestServer* https_server() { return &https_server_; }
+
+  GURL EchoCookiesUrl(const std::string& host) const {
+    return https_server_.GetURL(host, "/echoheader?Cookie");
+  }
+
+  std::string ExtractFrameContent(RenderFrameHost* frame) const {
+    return EvalJs(frame, "document.body.textContent").ExtractString();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+  net::test_server::EmbeddedTestServer https_server_;
+};
+
+IN_PROC_BROWSER_TEST_P(AncestorChainBitEnabledThirdPartyCookiesBlockedTest,
+                       TestSubresourceRedirects) {
+  // Initial frame tree A->B (B is an iframe)
+  // A cookie is set for site C.
+  // iframe B is navigated to site C.
+  // Frame tree becomes A->C (C is an iframe).
+  // Check if cookie set for C is present in C.
+
+  // Embed an iframe containing B in A to create initial frame tree A->B.
+  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
+                web_contents(), https_server(), base::StrCat({kHostA, "(%s)"}),
+                {0}, EchoCookiesUrl(kHostB)),
+            "None");
+
+  // Set SameSite=None partitioned cookie for kHostC from embedded iframe B.
+
+  net::CookiePartitionKey partition_key =
+      net::CookiePartitionKey::FromURLForTesting(
+          https_server()->GetURL(kHostA, "/"),
+          net::CookiePartitionKey::AncestorChainBit::kCrossSite);
+
+  ASSERT_TRUE(SetCookie(
+      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostC, "/"),
+      base::StrCat(
+          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;partitioned"}),
+      net::CookieOptions::SameSiteCookieContext(
+          net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE),
+      &partition_key));
+  // confirm that there is a cookie with kHostC url in the mojom cookie manager
+  // and that the cookie is partitioned and third party.
+  std::vector<net::CanonicalCookie> cookies = GetCanonicalCookies(
+      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostC, "/"),
+      net::CookiePartitionKeyCollection::FromOptional(
+          std::make_optional(partition_key)));
+  ASSERT_EQ(cookies.size(), 1u);
+  ASSERT_TRUE(cookies[0].IsPartitioned());
+  ASSERT_TRUE(cookies[0].PartitionKey()->IsThirdParty());
+
+  // Navigate embedded iframe B to C
+  ASSERT_TRUE(NavigateToURLFromRenderer(
+      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
+      EchoCookiesUrl(kHostC)));
+
+  // Extract cookie from C
+  EXPECT_THAT(
+      ExtractFrameContent(
+          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
+      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
+}
+
+IN_PROC_BROWSER_TEST_P(AncestorChainBitEnabledThirdPartyCookiesBlockedTest,
+                       TestTopLevelRedirects) {
+  // Navigate to Site A and set cookie on site A.
+  // Redirect from site A to site B and back to site A.
+  // Confirm cookie is present on site A after redirection.
+
+  ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
+  // Check to make sure that there are no cookies set on kHostA.
+  ASSERT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
+              "None");
+  net::CookiePartitionKey partition_key =
+      net::CookiePartitionKey::FromURLForTesting(
+          https_server()->GetURL(kHostA, "/"),
+          net::CookiePartitionKey::AncestorChainBit::kSameSite);
+
+  ASSERT_TRUE(SetCookie(
+      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
+      base::StrCat(
+          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;partitioned"}),
+      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
+      &partition_key));
+
+  // Perform redirect from site A to site B and back to site A.
+  ASSERT_TRUE(
+      NavigateToURL(web_contents(),
+                    RedirectUrl(https_server(), kHostB, EchoCookiesUrl(kHostA)),
+                    EchoCookiesUrl(kHostA)));
+
+  EXPECT_THAT(
+      ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
+      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
+}
+
+IN_PROC_BROWSER_TEST_P(
+    AncestorChainBitEnabledThirdPartyCookiesBlockedTest,
+    TestSameSiteEmbeddedResourceToCrossSiteEmbeddedResource) {
+  // Initial frame tree A1->A2 (A2 is an iframe)
+  // A cookie is set from top-level A1 for site B with kCrossSite ancestor chain
+  // bit. iframe A2 is navigated to site B. Frame tree becomes A1->B (B is an
+  // iframe). Check if cookie set from A1 is present in B.
+
+  // Embed an iframe containing A in A to create initial frame tree A->A.
+  ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
+                web_contents(), https_server(), base::StrCat({kHostA, "(%s)"}),
+                {0}, EchoCookiesUrl(kHostA)),
+            "None");
+
+  net::CookiePartitionKey partition_key =
+      net::CookiePartitionKey::FromURLForTesting(
+          https_server()->GetURL(kHostA, "/"),
+          net::CookiePartitionKey::AncestorChainBit::kCrossSite);
+
+  ASSERT_TRUE(SetCookie(
+      web_contents()->GetBrowserContext(), https_server()->GetURL(kHostB, "/"),
+      base::StrCat(
+          {kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
+      net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
+      &partition_key));
+
+  // Navigate embedded iframe A2 to B
+  ASSERT_TRUE(NavigateToURLFromRenderer(
+      ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
+      EchoCookiesUrl(kHostB)));
+
+  // Extract cookie from B
+  EXPECT_THAT(
+      ExtractFrameContent(
+          ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
+      net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
+}
+
 INSTANTIATE_TEST_SUITE_P(/* no label */,
                          HttpCookieBrowserTest,
                          ::testing::Bool());
 
+INSTANTIATE_TEST_SUITE_P(
+    /* no label */,
+    AncestorChainBitEnabledThirdPartyCookiesBlockedTest,
+    ::testing::Bool());
+
 }  // namespace
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/no_vary_search_helper_unittest.cc b/content/browser/preloading/prefetch/no_vary_search_helper_unittest.cc
index b3a1974..595556d 100644
--- a/content/browser/preloading/prefetch/no_vary_search_helper_unittest.cc
+++ b/content/browser/preloading/prefetch/no_vary_search_helper_unittest.cc
@@ -6,7 +6,7 @@
 
 #include "base/test/scoped_feature_list.h"
 #include "content/browser/preloading/prefetch/prefetch_container.h"
-#include "content/browser/preloading/prefetch/prefetch_test_utils.h"
+#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/public/test/test_renderer_host.h"
 #include "services/network/public/cpp/features.h"
diff --git a/content/browser/preloading/prefetch/prefetch_container_unittest.cc b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
index d58ea60..baa592fb 100644
--- a/content/browser/preloading/prefetch/prefetch_container_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
@@ -11,7 +11,7 @@
 #include "content/browser/preloading/prefetch/prefetch_features.h"
 #include "content/browser/preloading/prefetch/prefetch_probe_result.h"
 #include "content/browser/preloading/prefetch/prefetch_status.h"
-#include "content/browser/preloading/prefetch/prefetch_test_utils.h"
+#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
 #include "content/browser/preloading/prefetch/prefetch_type.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/public/browser/browser_context.h"
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
index 31b1a02..aead1e3 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
@@ -11,7 +11,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "content/browser/preloading/prefetch/prefetch_features.h"
 #include "content/browser/preloading/prefetch/prefetch_service.h"
-#include "content/browser/preloading/prefetch/prefetch_test_utils.h"
+#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
 #include "content/public/test/navigation_simulator.h"
 #include "content/public/test/test_browser_context.h"
 #include "content/test/test_render_frame_host.h"
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
index 8bd208a..d67e474 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
@@ -9,7 +9,7 @@
 #include "base/test/task_environment.h"
 #include "content/browser/preloading/prefetch/prefetch_features.h"
 #include "content/browser/preloading/prefetch/prefetch_response_reader.h"
-#include "content/browser/preloading/prefetch/prefetch_test_utils.h"
+#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/network/public/cpp/resource_request.h"
diff --git a/content/browser/preloading/prefetch/prefetch_test_utils.cc b/content/browser/preloading/prefetch/prefetch_test_util_internal.cc
similarity index 99%
rename from content/browser/preloading/prefetch/prefetch_test_utils.cc
rename to content/browser/preloading/prefetch/prefetch_test_util_internal.cc
index f7181fa6..636adf3 100644
--- a/content/browser/preloading/prefetch/prefetch_test_utils.cc
+++ b/content/browser/preloading/prefetch/prefetch_test_util_internal.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/preloading/prefetch/prefetch_test_utils.h"
+#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
 
 #include "base/run_loop.h"
 #include "base/time/time.h"
diff --git a/content/browser/preloading/prefetch/prefetch_test_utils.h b/content/browser/preloading/prefetch/prefetch_test_util_internal.h
similarity index 96%
rename from content/browser/preloading/prefetch/prefetch_test_utils.h
rename to content/browser/preloading/prefetch/prefetch_test_util_internal.h
index 08836a5..b9fd6ab 100644
--- a/content/browser/preloading/prefetch/prefetch_test_utils.h
+++ b/content/browser/preloading/prefetch/prefetch_test_util_internal.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 CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_TEST_UTILS_H_
-#define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_TEST_UTILS_H_
+#ifndef CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_TEST_UTIL_INTERNAL_H_
+#define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_TEST_UTIL_INTERNAL_H_
 
 #include <memory>
 #include <ostream>
@@ -132,4 +132,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_TEST_UTILS_H_
+#endif  // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_TEST_UTIL_INTERNAL_H_
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
index 2edce61..349b6af 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
@@ -25,7 +25,7 @@
 #include "content/browser/preloading/prefetch/prefetch_params.h"
 #include "content/browser/preloading/prefetch/prefetch_probe_result.h"
 #include "content/browser/preloading/prefetch/prefetch_service.h"
-#include "content/browser/preloading/prefetch/prefetch_test_utils.h"
+#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
 #include "content/browser/preloading/prefetch/prefetch_type.h"
 #include "content/browser/preloading/prefetch/prefetch_url_loader_helper.h"
 #include "content/browser/preloading/preloading.h"
diff --git a/content/browser/preloading/prerender/prerender_browsertest.cc b/content/browser/preloading/prerender/prerender_browsertest.cc
index 5a47250b..d1b801a4 100644
--- a/content/browser/preloading/prerender/prerender_browsertest.cc
+++ b/content/browser/preloading/prerender/prerender_browsertest.cc
@@ -40,7 +40,7 @@
 #include "content/browser/back_forward_cache_test_util.h"
 #include "content/browser/preloading/prefetch/prefetch_features.h"
 #include "content/browser/preloading/prefetch/prefetch_service.h"
-#include "content/browser/preloading/prefetch/prefetch_test_utils.h"
+#include "content/browser/preloading/prefetch/prefetch_test_util_internal.h"
 #include "content/browser/preloading/preloading.h"
 #include "content/browser/preloading/preloading_attempt_impl.h"
 #include "content/browser/preloading/preloading_data_impl.h"
diff --git a/content/browser/renderer_host/navigation_entry_impl.cc b/content/browser/renderer_host/navigation_entry_impl.cc
index 77874251..0de65101 100644
--- a/content/browser/renderer_host/navigation_entry_impl.cc
+++ b/content/browser/renderer_host/navigation_entry_impl.cc
@@ -19,7 +19,9 @@
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "components/url_formatter/url_formatter.h"
+#include "components/viz/host/host_frame_sink_manager.h"
 #include "content/browser/child_process_security_policy_impl.h"
+#include "content/browser/compositor/surface_utils.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_controller_impl.h"
 #include "content/browser/renderer_host/navigation_entry_restore_context_impl.h"
@@ -441,7 +443,19 @@
               ? InitialNavigationEntryState::kInitialNotForSynchronousAboutBlank
               : InitialNavigationEntryState::kNonInitial) {}
 
-NavigationEntryImpl::~NavigationEntryImpl() {}
+NavigationEntryImpl::~NavigationEntryImpl() {
+  if (same_document_navigation_entry_screenshot_token_.has_value()) {
+    // We get here if:
+    // - `DidCommitSameDocumentNavigation` sets the token, promising a
+    //    screenshot was supposed to arrive.
+    // - However the navigation entry was destroyed before the screenshot could
+    //   arrive.
+    viz::HostFrameSinkManager* manager = GetHostFrameSinkManager();
+    CHECK(manager);
+    manager->InvalidateCopyOutputReadyCallback(
+        same_document_navigation_entry_screenshot_token_.value());
+  }
+}
 
 int NavigationEntryImpl::GetUniqueID() {
   return unique_id_;
@@ -1243,4 +1257,16 @@
   return GetBaseURLForDataURL().is_empty() ? GURL() : GetVirtualURL();
 }
 
+void NavigationEntryImpl::SetSameDocumentNavigationEntryScreenshotToken(
+    const std::optional<blink::SameDocNavigationScreenshotDestinationToken>&
+        token) {
+  viz::HostFrameSinkManager* manager = GetHostFrameSinkManager();
+  CHECK(manager);
+  if (same_document_navigation_entry_screenshot_token_.has_value()) {
+    manager->InvalidateCopyOutputReadyCallback(
+        same_document_navigation_entry_screenshot_token_.value());
+  }
+  same_document_navigation_entry_screenshot_token_ = token;
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/navigation_entry_impl.h b/content/browser/renderer_host/navigation_entry_impl.h
index 81ce14a..62ac9ba 100644
--- a/content/browser/renderer_host/navigation_entry_impl.h
+++ b/content/browser/renderer_host/navigation_entry_impl.h
@@ -509,6 +509,15 @@
     return initial_navigation_entry_state_;
   }
 
+  void SetSameDocumentNavigationEntryScreenshotToken(
+      const std::optional<blink::SameDocNavigationScreenshotDestinationToken>&
+          token);
+
+  const std::optional<blink::SameDocNavigationScreenshotDestinationToken>&
+  same_document_navigation_entry_screenshot_token() const {
+    return same_document_navigation_entry_screenshot_token_;
+  }
+
  private:
   std::unique_ptr<NavigationEntryImpl> CloneAndReplaceInternal(
       scoped_refptr<FrameNavigationEntry> frame_entry,
@@ -647,6 +656,14 @@
   // See comment for the enum for explanation.
   InitialNavigationEntryState initial_navigation_entry_state_ =
       InitialNavigationEntryState::kNonInitial;
+
+  // Used to map a screenshot for the last frame of this navigation entry
+  // captured in Viz and sent back to the browser process. The token is set when
+  // `DidCommitSameDocumentNavigation` is received in the browser process from
+  // the renderer; and reset when its corresponding screenshot is received by
+  // the browser process from Viz.
+  std::optional<blink::SameDocNavigationScreenshotDestinationToken>
+      same_document_navigation_entry_screenshot_token_;
 };
 
 }  // namespace content
diff --git a/content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_browsertest.cc b/content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_browsertest.cc
index c1a77d7..f4bba8b 100644
--- a/content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_browsertest.cc
+++ b/content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_browsertest.cc
@@ -11,7 +11,9 @@
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "cc/test/pixel_test_utils.h"
+#include "components/viz/host/host_frame_sink_manager.h"
 #include "content/browser/browser_context_impl.h"
+#include "content/browser/compositor/surface_utils.h"
 #include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_cache.h"
 #include "content/browser/renderer_host/navigation_transitions/navigation_entry_screenshot_manager.h"
 #include "content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h"
@@ -33,11 +35,13 @@
 #include "content/shell/browser/shell_content_browser_client.h"
 #include "content/test/content_browser_test_utils_internal.h"
 #include "content/test/render_document_feature.h"
+#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/default_handlers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/android/view_android.h"
 #include "url/url_constants.h"
 
 namespace content {
@@ -63,14 +67,20 @@
 //   that its surface can be copied.
 void NavigateTabAndWaitForScreenshotCached(WebContents* tab,
                                            NavigationControllerImpl& controller,
-                                           const GURL& destination) {
+                                           const GURL& destination,
+                                           bool same_doc_nav = false) {
   const int num_request_before_nav =
       NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting();
   const int entries_count_before_nav = controller.GetEntryCount();
   ScopedScreenshotCapturedObserverForTesting observer(
       controller.GetLastCommittedEntryIndex());
   ASSERT_TRUE(NavigateToURL(tab, destination));
-  WaitForCopyableViewInWebContents(tab);
+  // We don't need to wait for the same-doc navigations. When the browser
+  // receives the screenshot for same-doc navigations, the renderer must have
+  // submitted a new frame. Using the observer on screenshot capture is enough.
+  if (!same_doc_nav) {
+    WaitForCopyableViewInWebContents(tab);
+  }
   observer.Wait();
   ASSERT_EQ(controller.GetEntryCount(), entries_count_before_nav + 1);
   ASSERT_EQ(
@@ -83,14 +93,17 @@
 void HistoryNavigateTabAndWaitForScreenshotCached(
     WebContents* tab,
     NavigationControllerImpl& controller,
-    int offset) {
+    int offset,
+    bool same_doc_nav = false) {
   const int num_request_before_nav =
       NavigationTransitionUtils::GetNumCopyOutputRequestIssuedForTesting();
   const int entries_count_before_nav = controller.GetEntryCount();
   ScopedScreenshotCapturedObserverForTesting observer(
       controller.GetLastCommittedEntryIndex());
   ASSERT_TRUE(HistoryGoToOffset(tab, offset));
-  WaitForCopyableViewInWebContents(tab);
+  if (!same_doc_nav) {
+    WaitForCopyableViewInWebContents(tab);
+  }
   observer.Wait();
   ASSERT_EQ(controller.GetEntryCount(), entries_count_before_nav);
   ASSERT_EQ(
@@ -171,13 +184,10 @@
 
 }  // namespace
 
-class NavigationEntryScreenshotBrowserTest
-    : public ContentBrowserTest,
-      public ::testing::WithParamInterface<
-          ScreenshotCaptureTestNavigationType> {
+class NavigationEntryScreenshotBrowserTestBase : public ContentBrowserTest {
  public:
-  NavigationEntryScreenshotBrowserTest() = default;
-  ~NavigationEntryScreenshotBrowserTest() override = default;
+  NavigationEntryScreenshotBrowserTestBase() = default;
+  ~NavigationEntryScreenshotBrowserTestBase() override = default;
 
   void SetUp() override {
     NavigationTransitionUtils::ResetNumCopyOutputRequestIssuedForTesting();
@@ -189,6 +199,118 @@
     ContentBrowserTest::TearDown();
   }
 
+  void SetUpOnMainThread() override {
+    ContentBrowserTest::SetUpOnMainThread();
+
+    ASSERT_TRUE(
+        base::FeatureList::IsEnabled(blink::features::kBackForwardTransitions));
+
+    host_resolver()->AddRule("*", "127.0.0.1");
+    embedded_test_server()->ServeFilesFromSourceDirectory(
+        GetTestDataFilePath());
+    net::test_server::RegisterDefaultHandlers(embedded_test_server());
+    SetupCrossSiteRedirector(embedded_test_server());
+
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+  static void ExpectBitmapRowsAreColor(
+      const SkBitmap& bitmap,
+      SkColor color,
+      std::optional<gfx::Rect> compare_region = std::nullopt) {
+    int num_pixel_mismatch = 0;
+    gfx::Rect err_bounding_box;
+
+    int row_start = 0;
+    int row_end = bitmap.height();
+    int col_start = 0;
+    int col_end = bitmap.width();
+
+    if (compare_region.has_value()) {
+      row_start = compare_region->y();
+      row_end = compare_region->bottom();
+      col_start = compare_region->x();
+      col_end = compare_region->right();
+    }
+
+    for (int r = row_start; r < row_end; ++r) {
+      for (int c = col_start; c < col_end; ++c) {
+        if (bitmap.getColor(c, r) != color) {
+          ++num_pixel_mismatch;
+          err_bounding_box.Union(gfx::Rect(c, r, 1, 1));
+        }
+      }
+    }
+    if (num_pixel_mismatch != 0) {
+      EXPECT_TRUE(false)
+          << "Number of pixel mismatches: " << num_pixel_mismatch
+          << "; error bounding box: " << err_bounding_box.ToString()
+          << "; bitmap size: "
+          << gfx::Size(bitmap.width(), bitmap.height()).ToString()
+          << "; expect color " << std::format("{:x}", color)
+          << "; actual bitmap " << cc::GetPNGDataUrl(bitmap);
+    }
+  }
+
+  void ExpectScreenshotIsColor(
+      NavigationEntryScreenshot* screenshot,
+      SkColor color,
+      std::optional<gfx::Rect> compare_region = std::nullopt) {
+    EXPECT_NE(screenshot, nullptr);
+    EXPECT_EQ(screenshot->GetDimensions(), GetScaledViewportSize());
+
+    auto bitmap = screenshot->GetBitmapForTesting();
+    ExpectBitmapRowsAreColor(bitmap, color, compare_region);
+  }
+
+  void AssertOrderedScreenshotsAre(
+      NavigationControllerImpl& controller,
+      const std::vector<std::optional<SkColor>>& expected_screenshots,
+      std::optional<gfx::Rect> compare_region = std::nullopt) {
+    ASSERT_EQ(controller.GetEntryCount(),
+              static_cast<int>(expected_screenshots.size()));
+    for (int index = 0; index < controller.GetEntryCount(); ++index) {
+      auto* entry = controller.GetEntryAtIndex(index);
+      if (expected_screenshots[index].has_value()) {
+        auto* screenshot = PreviewScreenshotForEntry(entry);
+        ExpectScreenshotIsColor(screenshot, expected_screenshots[index].value(),
+                                compare_region);
+      } else {
+        EXPECT_EQ(PreviewScreenshotForEntry(entry), nullptr);
+      }
+    }
+  }
+
+  gfx::Size GetScaledViewportSize() {
+    // Scale down the size to avoid memory pressure causing cache purging.
+    return ScaleToRoundedSize(
+        web_contents()->GetNativeView()->GetPhysicalBackingSize(),
+        /*scale=*/0.1);
+  }
+
+  size_t GetScaledViewportSizeInBytes() {
+    // 4 bytes per pixel.
+    return 4 * GetScaledViewportSize().Area64();
+  }
+
+  NavigationEntryScreenshotManager* GetManagerForTab(WebContents* tab) {
+    return BrowserContextImpl::From(tab->GetBrowserContext())
+        ->GetNavigationEntryScreenshotManager();
+  }
+
+  WebContentsImpl* web_contents() {
+    return static_cast<WebContentsImpl*>(shell()->web_contents());
+  }
+};
+
+class NavigationEntryScreenshotBrowserTest
+    : public NavigationEntryScreenshotBrowserTestBase,
+      public ::testing::WithParamInterface<
+          ScreenshotCaptureTestNavigationType> {
+ public:
+  NavigationEntryScreenshotBrowserTest() = default;
+  ~NavigationEntryScreenshotBrowserTest() override = default;
+
   void SetUpCommandLine(base::CommandLine* command_line) override {
     std::vector<base::test::FeatureRefAndParams> enabled_features = {
         {blink::features::kBackForwardTransitions, {}}};
@@ -211,22 +333,11 @@
     InitAndEnableRenderDocumentFeature(&scoped_feature_list_render_document_,
                                        RenderDocumentFeatureFullyEnabled()[0]);
 
-    ContentBrowserTest::SetUpCommandLine(command_line);
+    NavigationEntryScreenshotBrowserTestBase::SetUpCommandLine(command_line);
   }
 
   void SetUpOnMainThread() override {
-    ContentBrowserTest::SetUpOnMainThread();
-
-    ASSERT_TRUE(
-        base::FeatureList::IsEnabled(blink::features::kBackForwardTransitions));
-
-    host_resolver()->AddRule("*", "127.0.0.1");
-    embedded_test_server()->ServeFilesFromSourceDirectory(
-        GetTestDataFilePath());
-    net::test_server::RegisterDefaultHandlers(embedded_test_server());
-    SetupCrossSiteRedirector(embedded_test_server());
-
-    ASSERT_TRUE(embedded_test_server()->Start());
+    NavigationEntryScreenshotBrowserTestBase::SetUpOnMainThread();
 
     // The default WebContents has only the initial navigation entry. This
     // WebContents does not have a RWHV associated with it, making
@@ -259,77 +370,6 @@
         GetScaledViewportSize());
   }
 
-  static void ExpectBitmapRowsAreColor(const SkBitmap& bitmap,
-                                       int row_start,
-                                       int row_end_exclusive,
-                                       SkColor color) {
-    int num_pixel_mismatch = 0;
-    gfx::Rect err_bounding_box;
-    for (int r = row_start; r < row_end_exclusive; ++r) {
-      for (int c = 0; c < bitmap.width(); ++c) {
-        if (bitmap.getColor(c, r) != color) {
-          ++num_pixel_mismatch;
-          err_bounding_box.Union(gfx::Rect(c, r, 1, 1));
-        }
-      }
-    }
-    if (num_pixel_mismatch != 0) {
-      EXPECT_TRUE(false)
-          << "Number of pixel mismatches: " << num_pixel_mismatch
-          << "; error bounding box: " << err_bounding_box.ToString()
-          << "; bitmap size: "
-          << gfx::Size(bitmap.width(), bitmap.height()).ToString();
-    }
-  }
-
-  void ExpectScreenshotIsColor(NavigationEntryScreenshot* screenshot,
-                               SkColor color) {
-    EXPECT_NE(screenshot, nullptr);
-    EXPECT_EQ(screenshot->GetDimensions(), GetScaledViewportSize());
-
-    auto bitmap = screenshot->GetBitmapForTesting();
-    ExpectBitmapRowsAreColor(bitmap, /*row_start=*/0,
-                             /*row_end_exclusive=*/bitmap.height(), color);
-  }
-
-  void AssertOrderedScreenshotsAre(
-      NavigationControllerImpl& controller,
-      const std::vector<std::optional<SkColor>>& expected_screenshots) {
-    ASSERT_EQ(controller.GetEntryCount(),
-              static_cast<int>(expected_screenshots.size()));
-    for (int index = 0; index < controller.GetEntryCount(); ++index) {
-      auto* entry = controller.GetEntryAtIndex(index);
-      if (expected_screenshots[index].has_value()) {
-        auto* screenshot = PreviewScreenshotForEntry(entry);
-        ExpectScreenshotIsColor(screenshot,
-                                expected_screenshots[index].value());
-      } else {
-        EXPECT_EQ(PreviewScreenshotForEntry(entry), nullptr);
-      }
-    }
-  }
-
-  gfx::Size GetScaledViewportSize() {
-    // Scale down the size to avoid memory pressure causing cache purging.
-    return ScaleToRoundedSize(
-        web_contents()->GetRenderWidgetHostView()->GetVisibleViewportSize(),
-        /*scale=*/0.1);
-  }
-
-  size_t GetScaledViewportSizeInBytes() {
-    // 4 bytes per pixel.
-    return 4 * GetScaledViewportSize().Area64();
-  }
-
-  NavigationEntryScreenshotManager* GetManagerForTab(WebContents* tab) {
-    return BrowserContextImpl::From(tab->GetBrowserContext())
-        ->GetNavigationEntryScreenshotManager();
-  }
-
-  WebContentsImpl* web_contents() {
-    return static_cast<WebContentsImpl*>(shell()->web_contents());
-  }
-
   std::string GetNextHost() { return host_getter_->Get(); }
 
   GURL GetNextUrl(std::string_view path) {
@@ -978,22 +1018,24 @@
 
   // Expect the embedder's color matches.
   NavigationEntryScreenshotBrowserTest::ExpectBitmapRowsAreColor(
-      bitmap, /*row_start=*/0, /*row_end_exclusive=*/half_height, embedder);
+      bitmap, embedder, gfx::Rect(0, 0, bitmap.width(), half_height));
 
   // Expect the iframe's color matches. Skip checking the middle row if the
   // height is an odd number.
   int iframe_height_start = is_height_odd ? half_height + 1 : half_height;
   NavigationEntryScreenshotBrowserTest::ExpectBitmapRowsAreColor(
-      bitmap, /*row_start=*/iframe_height_start,
-      /*row_end_exclusive=*/bitmap.height(), iframe);
+      bitmap, iframe,
+      gfx::Rect(0, iframe_height_start, bitmap.width(), half_height));
 }
 }  // namespace
 
 // Asserts that no screenshots captured for the navigations of iframes.
 //
 // TODO(crbug.com/40896219): Support iframe navigations.
+//
+// TODO(crbug.com/340929354): Reenable the test.
 IN_PROC_BROWSER_TEST_P(NavigationEntryScreenshotBrowserTest,
-                       SameOriginIFrame_NotCaptured) {
+                       DISABLED_SameOriginIFrame_NotCaptured) {
   const size_t page_size = GetScaledViewportSizeInBytes();
   const size_t memory_budget = 10 * page_size;
   auto* manager = GetManagerForTab(web_contents());
@@ -1134,4 +1176,127 @@
                                              .enable_bfcache = false}}),
     &DescribeNavType);
 
+namespace {
+
+void NavigateTabAndWaitForScreenshotCachedSameDoc(
+    WebContents* tab,
+    NavigationControllerImpl& controller,
+    const GURL& destination) {
+  NavigateTabAndWaitForScreenshotCached(tab, controller, destination, true);
+}
+
+void HistoryNavigateTabAndWaitForScreenshotCachedSameDoc(
+    WebContents* tab,
+    NavigationControllerImpl& controller,
+    int offset) {
+  HistoryNavigateTabAndWaitForScreenshotCached(tab, controller, offset, true);
+}
+
+}  // namespace
+
+class SameDocNavigationEntryScreenshotBrowserTest
+    : public NavigationEntryScreenshotBrowserTestBase {
+ public:
+  SameDocNavigationEntryScreenshotBrowserTest() = default;
+  ~SameDocNavigationEntryScreenshotBrowserTest() override = default;
+
+  void SetUp() override {
+    if (base::SysInfo::GetAndroidHardwareEGL() == "emulation") {
+      // crbug.com/337886037 and crrev.com/c/5504854/comment/b81b8fb6_95fb1381/:
+      // The CopyOutputRequests crash the GPU process. ANGLE is exporting the
+      // native fence support on Android emulators but it doesn't work properly.
+      GTEST_SKIP();
+    }
+    NavigationEntryScreenshotBrowserTestBase::SetUp();
+  }
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    std::vector<base::test::FeatureRefAndParams> enabled_features = {
+        {blink::features::kBackForwardTransitions, {}},
+        {blink::features::kIncrementLocalSurfaceIdForMainframeSameDocNavigation,
+         {}}};
+
+    scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features, {});
+
+    // Disable the vertical scroll bar, otherwise they might show up on the
+    // screenshot, making the test flaky.
+    command_line->AppendSwitch(switches::kHideScrollbars);
+    NavigationEntryScreenshotBrowserTestBase::SetUpCommandLine(command_line);
+  }
+
+  void SetUpOnMainThread() override {
+    NavigationEntryScreenshotBrowserTestBase::SetUpOnMainThread();
+
+    ASSERT_TRUE(NavigateToURL(web_contents(), embedded_test_server()->GetURL(
+                                                  "/changing_color.html")));
+    WaitForCopyableViewInWebContents(web_contents());
+
+    mojo::ScopedAllowSyncCallForTesting allowed_for_testing;
+    GetHostFrameSinkManager()->SetSameDocNavigationScreenshotSizeForTesting(
+        GetScaledViewportSize());
+  }
+
+  gfx::Rect GetCompareRegion() { return gfx::Rect(GetScaledViewportSize()); }
+
+  GURL GetURL(const std::string& hash) {
+    return embedded_test_server()->GetURL("/changing_color.html" + hash);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(SameDocNavigationEntryScreenshotBrowserTest, Basic) {
+  const size_t page_size = GetScaledViewportSizeInBytes();
+  const size_t memory_budget = 10 * page_size;
+  auto* manager = GetManagerForTab(web_contents());
+  manager->SetMemoryBudgetForTesting(memory_budget);
+  auto& controller = web_contents()->GetController();
+
+  {
+    SCOPED_TRACE("[red*] -> [red&, green*]");
+    NavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(), controller,
+                                                 GetURL("#green"));
+    AssertOrderedScreenshotsAre(controller, {SK_ColorRED, std::nullopt},
+                                GetCompareRegion());
+    ASSERT_EQ(manager->GetCurrentCacheSize(), 1 * page_size);
+  }
+  {
+    SCOPED_TRACE("[red&, green*] -> [red&, green&, blue*]");
+    NavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(), controller,
+                                                 GetURL("#blue"));
+    AssertOrderedScreenshotsAre(controller,
+                                {SK_ColorRED, SK_ColorGREEN, std::nullopt},
+                                GetCompareRegion());
+    ASSERT_EQ(manager->GetCurrentCacheSize(), 2 * page_size);
+  }
+  {
+    SCOPED_TRACE("[red&, green&, blue*] -> [red&, green&, blue&, red*]");
+    NavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(), controller,
+                                                 GetURL("#red"));
+    AssertOrderedScreenshotsAre(
+        controller, {SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, std::nullopt},
+        GetCompareRegion());
+    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
+  }
+  {
+    SCOPED_TRACE("[red&, green&, blue&, red*] -> [red&, green&, blue*, red&]");
+    HistoryNavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(),
+                                                        controller, -1);
+    AssertOrderedScreenshotsAre(
+        controller, {SK_ColorRED, SK_ColorGREEN, std::nullopt, SK_ColorRED},
+        GetCompareRegion());
+    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
+  }
+  {
+    SCOPED_TRACE("[red&, green&, blue*, red&] -> [red*, green&, blue&, red&]");
+    HistoryNavigateTabAndWaitForScreenshotCachedSameDoc(web_contents(),
+                                                        controller, -2);
+    AssertOrderedScreenshotsAre(
+        controller, {std::nullopt, SK_ColorGREEN, SK_ColorBLUE, SK_ColorRED},
+        GetCompareRegion());
+    ASSERT_EQ(manager->GetCurrentCacheSize(), 3 * page_size);
+  }
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/navigation_transitions/navigation_transition_utils.cc b/content/browser/renderer_host/navigation_transitions/navigation_transition_utils.cc
index d2eb65d..928142a0 100644
--- a/content/browser/renderer_host/navigation_transitions/navigation_transition_utils.cc
+++ b/content/browser/renderer_host/navigation_transitions/navigation_transition_utils.cc
@@ -4,6 +4,9 @@
 
 #include "content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h"
 
+#include "components/viz/common/frame_sinks/copy_output_result.h"
+#include "components/viz/host/host_frame_sink_manager.h"
+#include "content/browser/compositor/surface_utils.h"
 #include "content/browser/renderer_host/frame_tree.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request.h"
@@ -55,30 +58,34 @@
   GetTestScreenshotCallback().Run(index, test_copy, requested);
 }
 
-void CacheScreenshotImpl(base::WeakPtr<NavigationControllerImpl> controller,
-                         int navigation_entry_id,
-                         const SkBitmap& bitmap) {
-  if (!controller) {
-    // The tab was destroyed by the time we receive the bitmap from the GPU.
-    return;
+// Returns the first entry that matches `destination_token`. Returns null if no
+// match is found.
+NavigationEntryImpl* GetEntryForToken(
+    NavigationControllerImpl* controller,
+    const blink::SameDocNavigationScreenshotDestinationToken&
+        destination_token) {
+  for (int i = 0; i < controller->GetEntryCount(); ++i) {
+    if (auto* entry = controller->GetEntryAtIndex(i);
+        entry->same_document_navigation_entry_screenshot_token() ==
+        destination_token) {
+      return entry;
+    }
   }
+  return nullptr;
+}
 
-  NavigationEntryImpl* entry =
-      controller->GetEntryWithUniqueID(navigation_entry_id);
-  if (!entry) {
-    // The entry was deleted by the time we received the bitmap from the GPU.
-    // This can happen by clearing the session history, or when the
-    // `NavigationEntry` was replaced or deleted, etc.
-    return;
-  }
+void CacheScreenshotImpl(NavigationControllerImpl& controller,
+                         NavigationEntryImpl& entry,
+                         const SkBitmap& bitmap) {
+  auto navigation_entry_id = entry.GetUniqueID();
 
   if (GetTestScreenshotCallback()) {
     InvokeTestCallback(
-        controller->GetEntryIndexWithUniqueID(navigation_entry_id), bitmap,
+        controller.GetEntryIndexWithUniqueID(navigation_entry_id), bitmap,
         true);
   }
 
-  if (entry == controller->GetLastCommittedEntry()) {
+  if (&entry == controller.GetLastCommittedEntry()) {
     // TODO(crbug.com/40278616): We shouldn't cache the screenshot into
     // the navigation entry if the entry is re-navigated after we send out the
     // copy request. See the two cases below.
@@ -100,7 +107,7 @@
   if (bitmap.drawsNothing()) {
     // The GPU is not able to produce a valid bitmap. This is an error case.
     LOG(ERROR) << "Cannot generate a valid bitmap for entry "
-               << entry->GetUniqueID() << " url " << entry->GetURL();
+               << entry.GetUniqueID() << " url " << entry.GetURL();
     return;
   }
 
@@ -108,23 +115,29 @@
   immutable_copy.setImmutable();
 
   auto screenshot = std::make_unique<NavigationEntryScreenshot>(
-      immutable_copy, entry->GetUniqueID());
+      immutable_copy, entry.GetUniqueID());
   NavigationEntryScreenshotCache* cache =
-      controller->GetNavigationEntryScreenshotCache();
-  cache->SetScreenshot(entry, std::move(screenshot));
+      controller.GetNavigationEntryScreenshotCache();
+  cache->SetScreenshot(&entry, std::move(screenshot));
 }
 
-void CacheScreenshot(base::WeakPtr<NavigationControllerImpl> controller,
-                     int navigation_entry_id,
-                     const SkBitmap& bitmap) {
-  // `CacheScreenshot`, as the callback for `CopyFromExactSurface`, is not
-  // guaranteed to be executed on the same thread that it was submitted from
-  // (browser's UI thread). Since `NavigationEntryScreenshotCache` can only be
-  // accessed from the browser's UI thread, we explicitly post the caching task
-  // onto the UI thread. See https://crbug.com/1217049 for more context.
-  GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
-      ->PostTask(FROM_HERE, base::BindOnce(&CacheScreenshotImpl, controller,
-                                           navigation_entry_id, bitmap));
+void CacheScreenshotForCrossDocNavigations(
+    base::WeakPtr<NavigationControllerImpl> controller,
+    int navigation_entry_id,
+    const SkBitmap& bitmap) {
+  if (!controller) {
+    // The tab was destroyed by the time we receive the bitmap from the GPU.
+    return;
+  }
+  NavigationEntryImpl* entry =
+      controller->GetEntryWithUniqueID(navigation_entry_id);
+  if (!entry) {
+    // The entry was deleted by the time we received the bitmap from the GPU.
+    // This can happen by clearing the session history, or when the
+    // `NavigationEntry` was replaced or deleted, etc.
+    return;
+  }
+  CacheScreenshotImpl(*controller, *entry, bitmap);
 }
 
 // We only want to capture screenshots for navigation entries reachable via
@@ -203,21 +216,8 @@
 // be loaded might have different contents than when the screenshot was taken in
 // a previous load. A new screenshot should be taken when navigating away from
 // this entry again.
-void RemoveScreenshotFromDestination(
-    const NavigationRequest& navigation_request) {
-  NavigationEntry* destination_entry = navigation_request.GetNavigationEntry();
-
-  if (!destination_entry) {
-    // We don't always have a destination entry (e.g., a new (non-history)
-    // subframe navigation). However if this is a session history navigation, we
-    // most-likely have a destination entry to navigate toward, from which we
-    // need to purge any existing screenshot.
-    return;
-  }
-
-  NavigationControllerImpl& nav_controller =
-      navigation_request.frame_tree_node()->navigator().controller();
-
+void RemoveScreenshotFromDestination(NavigationControllerImpl& nav_controller,
+                                     NavigationEntry* destination_entry) {
   if (!nav_controller.frame_tree().is_primary()) {
     // Navigations in the non-primary FrameTree can still have a destination
     // entry (e.g., Prerender's initial document-fetch request will create a
@@ -237,6 +237,34 @@
     CHECK(successfully_removed);
   }
 }
+
+void CacheScreenshotForSameDocNavigations(
+    base::WeakPtr<NavigationControllerImpl> controller,
+    int navigation_entry_id,
+    const SkBitmap& bitmap) {
+  CHECK(AreBackForwardTransitionsEnabled());
+
+  if (!controller) {
+    // The tab was destroyed by the time we receive the bitmap from the GPU.
+    return;
+  }
+
+  auto* destination_entry =
+      controller->GetEntryWithUniqueID(navigation_entry_id);
+
+  if (!destination_entry) {
+    // The entry was deleted by the time we received the bitmap from the GPU.
+    // This can happen by clearing the session history, or when the
+    // `NavigationEntry` was replaced or deleted, etc.
+    return;
+  }
+
+  CacheScreenshotImpl(*controller, *destination_entry, bitmap);
+
+  destination_entry->SetSameDocumentNavigationEntryScreenshotToken(
+      std::nullopt);
+}
+
 }  // namespace
 
 void NavigationTransitionUtils::SetCapturedScreenshotSizeForTesting(
@@ -257,12 +285,15 @@
   GetTestScreenshotCallback() = std::move(screenshot_callback);
 }
 
-void NavigationTransitionUtils::CaptureNavigationEntryScreenshot(
-    const NavigationRequest& navigation_request) {
+void NavigationTransitionUtils::
+    CaptureNavigationEntryScreenshotForCrossDocumentNavigations(
+        const NavigationRequest& navigation_request) {
   if (!AreBackForwardTransitionsEnabled()) {
     return;
   }
 
+  CHECK(!navigation_request.IsSameDocument());
+
   // The current conditions for whether to capture a screenshot depend on
   // `NavigationRequest::GetRenderFrameHost()`, so for now we should only get
   // here after the `RenderFrameHost` has been selected for a successful
@@ -274,11 +305,22 @@
   // `is_same_rfh_or_early_commit`.
   CHECK(navigation_request.HasRenderFrameHost());
 
+  auto* destination_entry = navigation_request.GetNavigationEntry();
+  if (!destination_entry) {
+    // We don't always have a destination entry (e.g., a new (non-history)
+    // subframe navigation). However if this is a session history navigation, we
+    // most-likely have a destination entry to navigate toward, from which we
+    // need to purge any existing screenshot.
+    return;
+  }
+
   // Remove the screenshot from the destination before checking the conditions.
   // We might not capture for this navigation due to some conditions, but the
   // navigation still continues (to commit/finish), for which we need to remove
   // the screenshot from the destination entry.
-  RemoveScreenshotFromDestination(navigation_request);
+  RemoveScreenshotFromDestination(
+      navigation_request.frame_tree_node()->frame_tree().controller(),
+      destination_entry);
   if (!CanTraverseToPreviousEntryAfterNavigation(navigation_request)) {
     if (GetTestScreenshotCallback()) {
       InvokeTestCallbackForNoScreenshot(navigation_request);
@@ -307,7 +349,8 @@
   bool copied_via_delegate =
       navigation_request.GetDelegate()->MaybeCopyContentAreaAsBitmap(
           base::BindOnce(
-              &CacheScreenshot, nav_controller.GetWeakPtr(),
+              &CacheScreenshotForCrossDocNavigations,
+              nav_controller.GetWeakPtr(),
               nav_controller.GetLastCommittedEntry()->GetUniqueID()));
 
   if (!copied_via_delegate &&
@@ -339,10 +382,63 @@
 
   static_cast<RenderWidgetHostViewBase*>(rwhv)->CopyFromExactSurface(
       /*src_rect=*/gfx::Rect(), output_size,
-      base::BindOnce(&CacheScreenshot, nav_controller.GetWeakPtr(),
+      base::BindOnce(&CacheScreenshotForCrossDocNavigations,
+                     nav_controller.GetWeakPtr(),
                      nav_controller.GetLastCommittedEntry()->GetUniqueID()));
 
   ++g_num_copy_requests_issued_for_testing;
 }
 
+void NavigationTransitionUtils::SetSameDocumentNavigationEntryScreenshotToken(
+    const NavigationRequest& navigation_request,
+    const blink::SameDocNavigationScreenshotDestinationToken&
+        destination_token) {
+  if (!AreBackForwardTransitionsEnabled()) {
+    // The source of this call is from the renderer. We can't always trust the
+    // renderer thus fail safely.
+    return;
+  }
+  NavigationControllerImpl& nav_controller =
+      navigation_request.frame_tree_node()->navigator().controller();
+  if (GetEntryForToken(&nav_controller, destination_token)) {
+    // Again, can't always trust the renderer to send a non-duplicated token.
+    return;
+  }
+
+  CHECK(navigation_request.IsSameDocument());
+
+  if (auto* destination_entry = navigation_request.GetNavigationEntry()) {
+    RemoveScreenshotFromDestination(nav_controller, destination_entry);
+  } else {
+    // All renderer-initiated same-document navigations will not have a
+    // destination entry (see
+    // `NavigationRequest::CreateForSynchronousRendererCommit`).
+  }
+
+  if (!CanTraverseToPreviousEntryAfterNavigation(navigation_request)) {
+    return;
+  }
+
+  // NOTE: `destination_token` is to set on the last committed entry (the
+  // screenshot's destination), instead of the destination entry of this
+  // `navigation_request` (`navigation_request.GetNavigationEntry()`).
+
+  // We won't reach here if the renderer hasn't requested a CopyOutputRequest,
+  // since the token in the DidCommitSameDocNavigation message will be nullopt.
+  ++g_num_copy_requests_issued_for_testing;
+
+  // `blink::SameDocNavigationScreenshotDestinationToken` is guaranteed
+  // non-empty.
+  nav_controller.GetLastCommittedEntry()
+      ->SetSameDocumentNavigationEntryScreenshotToken(destination_token);
+
+  CHECK(GetHostFrameSinkManager());
+
+  GetHostFrameSinkManager()->SetOnCopyOutputReadyCallback(
+      destination_token,
+      base::BindOnce(&CacheScreenshotForSameDocNavigations,
+                     nav_controller.GetWeakPtr(),
+                     nav_controller.GetLastCommittedEntry()->GetUniqueID()));
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h b/content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h
index d0db9c9..4143c11 100644
--- a/content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h
+++ b/content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h
@@ -7,6 +7,7 @@
 
 #include "base/functional/callback.h"
 #include "content/common/content_export.h"
+#include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 namespace gfx {
@@ -25,9 +26,18 @@
   // Capture the `NavigationEntryScreenshot` for the old page, and store the
   // screenshot in the old page's NavigationEntry.
   // Should only be called immediately before the old page is unloaded.
-  static void CaptureNavigationEntryScreenshot(
+  static void CaptureNavigationEntryScreenshotForCrossDocumentNavigations(
       const NavigationRequest& navigation_request);
 
+  // Called when `DidCommitSameDocumentNavigation` arrives at the browser, and
+  // *before* the navigation commits. Ensures that a `NavigationEntryScreenshot`
+  // for the pre-navigation DOM state is cached when provided by the Viz
+  // process.
+  static void SetSameDocumentNavigationEntryScreenshotToken(
+      const NavigationRequest& navigation_request,
+      const blink::SameDocNavigationScreenshotDestinationToken&
+          destination_token);
+
   // Used by tests to deterministically validate the memory budgeting / eviction
   // logic.
   CONTENT_EXPORT static void SetCapturedScreenshotSizeForTesting(
@@ -40,8 +50,8 @@
   CONTENT_EXPORT static void ResetNumCopyOutputRequestIssuedForTesting();
 
   // Calls `screenshot_callback` with the index of the previous NavigationEntry
-  // when leaving a page, along with the generated bitmap captured by the
-  // CaptureNavigationEntryScreenshot function.
+  // when leaving a page, along with the generated bitmap captured captured for
+  // all navigations.
   CONTENT_EXPORT static void SetNavScreenshotCallbackForTesting(
       ScreenshotCallback screenshot_callback);
 };
diff --git a/content/browser/renderer_host/navigator.cc b/content/browser/renderer_host/navigator.cc
index 8feb01b..5c1702ff 100644
--- a/content/browser/renderer_host/navigator.cc
+++ b/content/browser/renderer_host/navigator.cc
@@ -505,8 +505,11 @@
   // TODO(crbug.com/40278956): Move this into
   // `RenderFrameHostManager::CommitPending` to accommodate both regular
   // navigations and early-commit.
-  NavigationTransitionUtils::CaptureNavigationEntryScreenshot(
-      *navigation_request);
+  if (!was_within_same_document) {
+    NavigationTransitionUtils::
+        CaptureNavigationEntryScreenshotForCrossDocumentNavigations(
+            *navigation_request);
+  }
 
   if (ui::PageTransitionIsMainFrame(params.transition)) {
     // Run tasks that must execute just before the commit.
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 4413fdd..325edea 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -126,6 +126,7 @@
 #include "content/browser/renderer_host/navigation_metrics_utils.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/navigation_state_keep_alive.h"
+#include "content/browser/renderer_host/navigation_transitions/navigation_transition_utils.h"
 #include "content/browser/renderer_host/navigator.h"
 #include "content/browser/renderer_host/page_delegate.h"
 #include "content/browser/renderer_host/private_network_access_util.h"
@@ -13967,6 +13968,14 @@
                                ukm::SourceIdType::NAVIGATION_ID));
   }
 
+  if (is_same_document_navigation &&
+      same_document_params->navigation_entry_screenshot_destination
+          .has_value()) {
+    NavigationTransitionUtils::SetSameDocumentNavigationEntryScreenshotToken(
+        *(navigation_request.get()),
+        same_document_params->navigation_entry_screenshot_destination.value());
+  }
+
   // TODO(crbug.com/40150370): Do not pass |params| to DidNavigate().
   NavigationRequest* raw_navigation_request = navigation_request.get();
   raw_navigation_request->frame_tree_node()->navigator().DidNavigate(
diff --git a/content/browser/resources/attribution_reporting/attribution_internals_table.ts b/content/browser/resources/attribution_reporting/attribution_internals_table.ts
index 429a911f..3836d40 100644
--- a/content/browser/resources/attribution_reporting/attribution_internals_table.ts
+++ b/content/browser/resources/attribution_reporting/attribution_internals_table.ts
@@ -170,9 +170,22 @@
       this.clearRows();
     }
 
-    if (this.getId_) {
-      this.updateRows([data]);
-      return;
+    let tr: DataRowElement<T>|undefined;
+
+    const id = this.getId_ ? this.getId_(data, /*updated=*/ true) : undefined;
+    if (id !== undefined) {
+      tr = Array.prototype.find.call(
+          this.dataRows_(),
+          tr => id === this.getId_!(tr.data, /*updated=*/ false));
+
+      if (tr !== undefined) {
+        tr.data = data;
+        this.cols_!.forEach((render, idx) => render(tr!.cells[idx]!, data));
+      }
+    }
+
+    if (tr === undefined) {
+      tr = this.newRow_(data);
     }
 
     let nextTr: DataRowElement<T>|undefined;
@@ -182,7 +195,6 @@
           this.dataRows_(), tr => this.compare_!(tr.data, data) > 0);
     }
 
-    const tr = this.newRow_(data);
     if (nextTr) {
       nextTr.before(tr);
     } else {
diff --git a/content/browser/webid/fedcm_metrics.h b/content/browser/webid/fedcm_metrics.h
index c7957d4..ee54133 100644
--- a/content/browser/webid/fedcm_metrics.h
+++ b/content/browser/webid/fedcm_metrics.h
@@ -23,70 +23,74 @@
 using RpMode = blink::mojom::RpMode;
 
 // This enum describes the status of a request id token call to the FedCM API.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum class FedCmRequestIdTokenStatus {
   // Don't change the meaning or the order of these values because they are
   // being recorded in metrics and in sync with the counterpart in enums.xml.
-  kSuccessUsingTokenInHttpResponse,
-  kTooManyRequests,
-  kAborted,
-  kUnhandledRequest,
-  kIdpNotPotentiallyTrustworthy,
-  kNotSelectAccount,
-  kConfigHttpNotFound,
-  kConfigNoResponse,
-  kConfigInvalidResponse,
-  kClientMetadataHttpNotFound,     // obsolete
-  kClientMetadataNoResponse,       // obsolete
-  kClientMetadataInvalidResponse,  // obsolete
-  kAccountsHttpNotFound,
-  kAccountsNoResponse,
-  kAccountsInvalidResponse,
-  kIdTokenHttpNotFound,
-  kIdTokenNoResponse,
-  kIdTokenInvalidResponse,
-  kIdTokenInvalidRequest,                  // obsolete
-  kClientMetadataMissingPrivacyPolicyUrl,  // obsolete
-  kThirdPartyCookiesBlocked,               // obsolete
-  kDisabledInSettings,
-  kDisabledInFlags,
-  kWellKnownHttpNotFound,
-  kWellKnownNoResponse,
-  kWellKnownInvalidResponse,
-  kConfigNotInWellKnown,
-  kWellKnownTooBig,
-  kDisabledEmbargo,
-  kUserInterfaceTimedOut,  // obsolete
-  kRpPageNotVisible,
-  kShouldEmbargo,
-  kNotSignedInWithIdp,
-  kAccountsListEmpty,
-  kWellKnownListEmpty,
-  kWellKnownInvalidContentType,
-  kConfigInvalidContentType,
-  kAccountsInvalidContentType,
-  kIdTokenInvalidContentType,
-  kSilentMediationFailure,
-  kIdTokenIdpErrorResponse,
-  kIdTokenCrossSiteIdpErrorResponse,
-  kOtherIdpChosen,
-  kMissingTransientUserActivation,
-  kReplacedByButtonMode,
-  kContinuationPopupClosedByUser,
-  kSuccessUsingIdentityProviderResolve,
-  kContinuationPopupClosedByIdentityProviderClose,
-  kInvalidFieldsSpecified,
+  kSuccessUsingTokenInHttpResponse = 0,
+  kTooManyRequests = 1,
+  kAborted = 2,
+  kUnhandledRequest = 3,
+  kIdpNotPotentiallyTrustworthy = 4,
+  kNotSelectAccount = 5,
+  kConfigHttpNotFound = 6,
+  kConfigNoResponse = 7,
+  kConfigInvalidResponse = 8,
+  kClientMetadataHttpNotFound = 9,      // obsolete
+  kClientMetadataNoResponse = 10,       // obsolete
+  kClientMetadataInvalidResponse = 11,  // obsolete
+  kAccountsHttpNotFound = 12,
+  kAccountsNoResponse = 13,
+  kAccountsInvalidResponse = 14,
+  kIdTokenHttpNotFound = 15,
+  kIdTokenNoResponse = 16,
+  kIdTokenInvalidResponse = 17,
+  kIdTokenInvalidRequest = 18,                  // obsolete
+  kClientMetadataMissingPrivacyPolicyUrl = 19,  // obsolete
+  kThirdPartyCookiesBlocked = 20,               // obsolete
+  kDisabledInSettings = 21,
+  kDisabledInFlags = 22,
+  kWellKnownHttpNotFound = 23,
+  kWellKnownNoResponse = 24,
+  kWellKnownInvalidResponse = 25,
+  kConfigNotInWellKnown = 26,
+  kWellKnownTooBig = 27,
+  kDisabledEmbargo = 28,
+  kUserInterfaceTimedOut = 29,  // obsolete
+  kRpPageNotVisible = 30,
+  kShouldEmbargo = 31,
+  kNotSignedInWithIdp = 32,
+  kAccountsListEmpty = 33,
+  kWellKnownListEmpty = 34,
+  kWellKnownInvalidContentType = 35,
+  kConfigInvalidContentType = 36,
+  kAccountsInvalidContentType = 37,
+  kIdTokenInvalidContentType = 38,
+  kSilentMediationFailure = 39,
+  kIdTokenIdpErrorResponse = 40,
+  kIdTokenCrossSiteIdpErrorResponse = 41,
+  kOtherIdpChosen = 42,
+  kMissingTransientUserActivation = 43,
+  kReplacedByButtonMode = 44,
+  kContinuationPopupClosedByUser = 45,
+  kSuccessUsingIdentityProviderResolve = 46,
+  kContinuationPopupClosedByIdentityProviderClose = 47,
+  kInvalidFieldsSpecified = 48,
 
   kMaxValue = kInvalidFieldsSpecified
 };
 
 // This enum describes whether user sign-in states between IDP and browser
 // match.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum class FedCmSignInStateMatchStatus {
   // Don't change the meaning or the order of these values because they are
   // being recorded in metrics and in sync with the counterpart in enums.xml.
-  kMatch,
-  kIdpClaimedSignIn,
-  kBrowserObservedSignIn,
+  kMatch = 0,
+  kIdpClaimedSignIn = 1,
+  kBrowserObservedSignIn = 2,
 
   kMaxValue = kBrowserObservedSignIn
 };
@@ -94,56 +98,62 @@
 // This enum describes whether the browser's knowledge of whether the user is
 // signed into the IDP based on observing signin/signout HTTP headers matches
 // the information returned by the accounts endpoint.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum class FedCmIdpSigninMatchStatus {
   // Don't change the meaning or the order of these values because they are
   // being recorded in metrics and in sync with the counterpart in enums.xml.
-  kMatchWithAccounts,
-  kMatchWithoutAccounts,
-  kUnknownStatusWithAccounts,
-  kUnknownStatusWithoutAccounts,
-  kMismatchWithNetworkError,
-  kMismatchWithNoContent,
-  kMismatchWithInvalidResponse,
-  kMismatchWithUnexpectedAccounts,
+  kMatchWithAccounts = 0,
+  kMatchWithoutAccounts = 1,
+  kUnknownStatusWithAccounts = 2,
+  kUnknownStatusWithoutAccounts = 3,
+  kMismatchWithNetworkError = 4,
+  kMismatchWithNoContent = 5,
+  kMismatchWithInvalidResponse = 6,
+  kMismatchWithUnexpectedAccounts = 7,
 
   kMaxValue = kMismatchWithUnexpectedAccounts
 };
 
 // This enum describes the type of frame that invokes a FedCM API.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum class FedCmRequesterFrameType {
   // Do not change the meaning or order of these values since they are being
   // recorded in metrics and in sync with the counterpart in enums.xml.
-  kMainFrame,
-  kSameSiteIframe,
-  kCrossSiteIframe,
+  kMainFrame = 0,
+  kSameSiteIframe = 1,
+  kCrossSiteIframe = 2,
 
   kMaxValue = kCrossSiteIframe
 };
 
 // This enum describes the status of a disconnect call to the FedCM API.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum class FedCmDisconnectStatus {
   // Don't change the meaning or the order of these values because they are
   // being recorded in metrics and in sync with the counterpart in enums.xml.
-  kSuccess,
-  kTooManyRequests,
-  kUnhandledRequest,
-  kNoAccountToDisconnect,
-  kDisconnectUrlIsCrossOrigin,
-  kDisconnectFailedOnServer,
-  kConfigHttpNotFound,
-  kConfigNoResponse,
-  kConfigInvalidResponse,
-  kDisabledInSettings,
-  kDisabledInFlags,
-  kWellKnownHttpNotFound,
-  kWellKnownNoResponse,
-  kWellKnownInvalidResponse,
-  kWellKnownListEmpty,
-  kConfigNotInWellKnown,
-  kWellKnownTooBig,
-  kWellKnownInvalidContentType,
-  kConfigInvalidContentType,
-  kIdpNotPotentiallyTrustworthy,
+  kSuccess = 0,
+  kTooManyRequests = 1,
+  kUnhandledRequest = 2,
+  kNoAccountToDisconnect = 3,
+  kDisconnectUrlIsCrossOrigin = 4,
+  kDisconnectFailedOnServer = 5,
+  kConfigHttpNotFound = 6,
+  kConfigNoResponse = 7,
+  kConfigInvalidResponse = 8,
+  kDisabledInSettings = 9,
+  kDisabledInFlags = 10,
+  kWellKnownHttpNotFound = 11,
+  kWellKnownNoResponse = 12,
+  kWellKnownInvalidResponse = 13,
+  kWellKnownListEmpty = 14,
+  kConfigNotInWellKnown = 15,
+  kWellKnownTooBig = 16,
+  kWellKnownInvalidContentType = 17,
+  kConfigInvalidContentType = 18,
+  kIdpNotPotentiallyTrustworthy = 19,
 
   kMaxValue = kIdpNotPotentiallyTrustworthy
 };
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index bd1d977..0ee2fc0 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -321,11 +321,6 @@
     "java/src/org/chromium/content/browser/picker/TwoFieldDatePickerDialog.java",
     "java/src/org/chromium/content/browser/picker/WeekPicker.java",
     "java/src/org/chromium/content/browser/picker/WeekPickerDialog.java",
-    "java/src/org/chromium/content/browser/remoteobjects/RemoteObjectAuditorImpl.java",
-    "java/src/org/chromium/content/browser/remoteobjects/RemoteObjectHostImpl.java",
-    "java/src/org/chromium/content/browser/remoteobjects/RemoteObjectImpl.java",
-    "java/src/org/chromium/content/browser/remoteobjects/RemoteObjectInjector.java",
-    "java/src/org/chromium/content/browser/remoteobjects/RemoteObjectRegistry.java",
     "java/src/org/chromium/content/browser/selection/LGEmailActionModeWorkaroundImpl.java",
     "java/src/org/chromium/content/browser/selection/MagnifierAnimator.java",
     "java/src/org/chromium/content/browser/selection/MagnifierSurfaceControl.java",
@@ -662,7 +657,6 @@
     "javatests/src/org/chromium/content/browser/input/StylusGestureEndToEndTest.java",
     "javatests/src/org/chromium/content/browser/input/TextSuggestionMenuTest.java",
     "javatests/src/org/chromium/content/browser/picker/DateTimePickerDialogTest.java",
-    "javatests/src/org/chromium/content/browser/remoteobjects/RemoteObjectHostImplTest.java",
     "javatests/src/org/chromium/content/browser/scheduler/NativePostTaskTest.java",
     "javatests/src/org/chromium/content/browser/scheduler/UiThreadSchedulerTest.java",
     "javatests/src/org/chromium/content/browser/scheduler/UncaughtExceptionTest.java",
@@ -701,8 +695,6 @@
     "junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java",
     "junit/src/org/chromium/content/browser/input/ThreadedInputConnectionTest.java",
     "junit/src/org/chromium/content/browser/picker/DateDialogNormalizerTest.java",
-    "junit/src/org/chromium/content/browser/remoteobjects/RemoteObjectImplTest.java",
-    "junit/src/org/chromium/content/browser/remoteobjects/RemoteObjectRegistryTest.java",
     "junit/src/org/chromium/content/browser/selection/MagnifierAnimatorTest.java",
     "junit/src/org/chromium/content/browser/selection/SelectActionMenuHelperTest.java",
     "junit/src/org/chromium/content/browser/selection/SelectionMenuCachedResultTest.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java b/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java
index c1f1a2c4..0d0259a 100644
--- a/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.java
@@ -12,7 +12,6 @@
 
 import org.chromium.base.UserData;
 import org.chromium.build.annotations.DoNotInline;
-import org.chromium.content.browser.remoteobjects.RemoteObjectInjector;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
 import org.chromium.content_public.browser.JavascriptInjector;
@@ -37,21 +36,17 @@
     @DoNotInline private final Set<Object> mRetainedObjects = new HashSet<>();
     private final Map<String, Pair<Object, Class>> mInjectedObjects = new HashMap<>();
     private long mNativePtr;
-    private RemoteObjectInjector mInjector;
-    private Boolean mUseMojo;
 
     /**
      * @param webContents {@link WebContents} object.
-     * @param useMojo Whether to use {@link RemoteObjectInjector} methods
-     * @return {@link JavascriptInjector} object used for the give WebContents.
-     *         Creates one if not present.
+     * @return {@link JavascriptInjector} object used for the give WebContents. Creates one if not
+     *     present.
      */
-    public static JavascriptInjector fromWebContents(WebContents webContents, boolean useMojo) {
+    public static JavascriptInjector fromWebContents(WebContents webContents) {
         JavascriptInjectorImpl javascriptInjector =
                 ((WebContentsImpl) webContents)
                         .getOrSetUserData(
                                 JavascriptInjectorImpl.class, UserDataFactoryLazyHolder.INSTANCE);
-        javascriptInjector.setUseMojo(useMojo);
         return javascriptInjector;
     }
 
@@ -59,16 +54,6 @@
         mNativePtr =
                 JavascriptInjectorImplJni.get()
                         .init(JavascriptInjectorImpl.this, webContents, mRetainedObjects);
-        mInjector = new RemoteObjectInjector(webContents);
-        webContents.addObserver(mInjector);
-    }
-
-    public void setUseMojo(boolean useMojo) {
-        if (mUseMojo == null) {
-            mUseMojo = useMojo;
-        } else {
-            assert mUseMojo == useMojo;
-        }
     }
 
     @CalledByNative
@@ -83,10 +68,7 @@
 
     @Override
     public void setAllowInspection(boolean allow) {
-        assert mUseMojo != null;
-        if (mUseMojo) {
-            mInjector.setAllowInspection(allow);
-        } else if (mNativePtr != 0) {
+        if (mNativePtr != 0) {
             JavascriptInjectorImplJni.get()
                     .setAllowInspection(mNativePtr, JavascriptInjectorImpl.this, allow);
         }
@@ -97,10 +79,7 @@
             Object object, String name, Class<? extends Annotation> requiredAnnotation) {
         if (object == null) return;
 
-        assert mUseMojo != null;
-        if (mUseMojo) {
-            mInjector.addInterface(object, name, requiredAnnotation);
-        } else if (mNativePtr != 0) {
+        if (mNativePtr != 0) {
             mInjectedObjects.put(name, new Pair<Object, Class>(object, requiredAnnotation));
             JavascriptInjectorImplJni.get()
                     .addInterface(
@@ -114,15 +93,10 @@
 
     @Override
     public void removeInterface(String name) {
-        assert mUseMojo != null;
-        if (mUseMojo) {
-            mInjector.removeInterface(name);
-        } else {
-            mInjectedObjects.remove(name);
-            if (mNativePtr != 0) {
-                JavascriptInjectorImplJni.get()
-                        .removeInterface(mNativePtr, JavascriptInjectorImpl.this, name);
-            }
+        mInjectedObjects.remove(name);
+        if (mNativePtr != 0) {
+            JavascriptInjectorImplJni.get()
+                    .removeInterface(mNativePtr, JavascriptInjectorImpl.this, name);
         }
     }
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/COMMON_METADATA b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/COMMON_METADATA
deleted file mode 100644
index 12293d9..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/COMMON_METADATA
+++ /dev/null
@@ -1,7 +0,0 @@
-monorail: {
-  component: "Mobile>WebView"
-}
-team_email: "android-webview-dev@chromium.org"
-buganizer_public: {
-  component_id: 1456456
-}
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/DIR_METADATA b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/DIR_METADATA
deleted file mode 100644
index de0888c0..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/DIR_METADATA
+++ /dev/null
@@ -1,2 +0,0 @@
-mixins: "//content/public/android/java/src/org/chromium/content/browser/remoteobjects/COMMON_METADATA"
-
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/OWNERS b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/OWNERS
deleted file mode 100644
index 56ec958..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-oksamyt@chromium.org
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/README.md b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/README.md
deleted file mode 100644
index 7fcc77ff..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Remote Objects
-
-This is an implementation of the Blink mojo interfaces which allow objects
-hosted out of process to be exposed to script. It is intended to ultimately
-migrate the Gin/Java bridge used to implement
-[`addJavascriptInterface`](https://developer.android.com/reference/android/webkit/WebView.html#addJavascriptInterface%28java.lang.Object,%20java.lang.String%29)
-in Android WebView.
-
-See also the [design doc](https://docs.google.com/document/d/1T8Zj_gZK7jHsy80Etk-Rw4hXMIW4QeaTtXjy5ZKP3X0/edit).
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectAuditorImpl.java b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectAuditorImpl.java
deleted file mode 100644
index 1c72e9b8b..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectAuditorImpl.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser.remoteobjects;
-
-import android.os.Process;
-import android.util.EventLog;
-
-final class RemoteObjectAuditorImpl implements RemoteObjectImpl.Auditor {
-    /**
-     * Event which should be logged if getClass is invoked.
-     * See frameworks/base/core/java/android/webkit/EventLogTags.logtags.
-     */
-    private static final int sObjectGetClassInvocationAttemptLogTag = 70151;
-
-    @Override
-    public void onObjectGetClassInvocationAttempt() {
-        EventLog.writeEvent(sObjectGetClassInvocationAttemptLogTag, Process.myUid());
-    }
-}
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectHostImpl.java b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectHostImpl.java
deleted file mode 100644
index 4f9a96b..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectHostImpl.java
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser.remoteobjects;
-
-import org.chromium.blink.mojom.RemoteObject;
-import org.chromium.blink.mojom.RemoteObjectHost;
-import org.chromium.mojo.bindings.InterfaceRequest;
-import org.chromium.mojo.system.MojoException;
-
-import java.lang.ref.WeakReference;
-
-/**
- * Exposes limited access to a set of Java objects over a Mojo interface.
- *
- * This object is split in two due to the need to ensure that the Mojo watcher does not keep the
- * WebView alive through strong references. It is expected that the WebView be destroyed when it is
- * collected by the runtime.
- *
- * To achieve this, all fields which might transitively point to the WebView are stored in a
- * separate object, RemoteObjectRegistry, held weakly. It is held alive via a retaining set which is
- * owned by the WebView. If the registry is collected, it means that the WebView is gone, and any
- * further access to the Mojo interface should fail.
- *
- * {@link RemoteObjectImpl} similarly holds its target weakly; it is held alive via the map held in
- * Internals.
- */
-class RemoteObjectHostImpl implements RemoteObjectHost {
-    /**
-     * Auditor passed on to {@link RemoteObjectImpl}.
-     * Should not hold any strong references that may lead to the contents.
-     */
-    private final RemoteObjectImpl.Auditor mAuditor;
-
-    /**
-     * The registry which owns the underlying target objects, if still alive.
-     * See the class comment.
-     */
-    private final WeakReference<RemoteObjectRegistry> mRegistry;
-
-    private boolean mAllowInspection;
-
-    RemoteObjectHostImpl(
-            RemoteObjectImpl.Auditor auditor,
-            RemoteObjectRegistry registry,
-            boolean allowInspection) {
-        mAuditor = auditor;
-        mRegistry = new WeakReference<>(registry);
-        mAllowInspection = allowInspection;
-    }
-
-    public void setAllowInspection(boolean allow) {
-        mAllowInspection = allow;
-    }
-
-    @Override
-    public void getObject(int objectId, InterfaceRequest<RemoteObject> request) {
-        try (InterfaceRequest<RemoteObject> autoClose = request) {
-            RemoteObjectRegistry registry = mRegistry.get();
-            if (registry == null) {
-                return;
-            }
-            Object target = registry.getObjectById(objectId);
-            if (target == null) {
-                return;
-            }
-            RemoteObjectImpl impl =
-                    new RemoteObjectImpl(
-                            target,
-                            registry.getSafeAnnotationClass(target),
-                            mAuditor,
-                            registry,
-                            mAllowInspection);
-            RemoteObject.MANAGER.bind(impl, request);
-        }
-    }
-
-    @Override
-    public void acquireObject(int objectId) {
-        RemoteObjectRegistry registry = mRegistry.get();
-        if (registry == null) {
-            return;
-        }
-        registry.refObjectById(objectId);
-    }
-
-    @Override
-    public void releaseObject(int objectId) {
-        RemoteObjectRegistry registry = mRegistry.get();
-        if (registry == null) {
-            return;
-        }
-        registry.unrefObjectById(objectId);
-    }
-
-    @Override
-    public void close() {
-        RemoteObjectRegistry registry = mRegistry.get();
-        if (registry == null) {
-            return;
-        }
-        registry.close();
-        mRegistry.clear();
-    }
-
-    @Override
-    public void onConnectionError(MojoException e) {
-        close();
-    }
-}
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectImpl.java b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectImpl.java
deleted file mode 100644
index e5d29ca9..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectImpl.java
+++ /dev/null
@@ -1,861 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser.remoteobjects;
-
-import androidx.annotation.IntDef;
-
-import org.chromium.blink.mojom.RemoteArrayType;
-import org.chromium.blink.mojom.RemoteInvocationArgument;
-import org.chromium.blink.mojom.RemoteInvocationError;
-import org.chromium.blink.mojom.RemoteInvocationResult;
-import org.chromium.blink.mojom.RemoteInvocationResultValue;
-import org.chromium.blink.mojom.RemoteObject;
-import org.chromium.blink.mojom.RemoteTypedArray;
-import org.chromium.blink.mojom.SingletonJavaScriptValue;
-import org.chromium.mojo.system.MojoException;
-import org.chromium.mojo_base.BigBufferUtil;
-import org.chromium.mojo_base.mojom.String16;
-
-import java.lang.annotation.Annotation;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Array;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.DoubleBuffer;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import java.nio.ShortBuffer;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.regex.Pattern;
-
-/**
- * Exposes limited access to a Java object over a Mojo interface.
- *
- * For LiveConnect references, an archived version is available at:
- * http://web.archive.org/web/20141022204935/http://jdk6.java.net/plugin2/liveconnect/
- */
-class RemoteObjectImpl implements RemoteObject {
-    /**
-     * Receives notification about events for auditing.
-     *
-     * Separated from this class proper to allow for unit testing in content_junit_tests, where the
-     * Android framework is not fully initialized.
-     *
-     * Implementations should take care not to hold a strong reference to anything that might keep
-     * the WebView contents alive due to a GC cycle.
-     */
-    interface Auditor {
-        void onObjectGetClassInvocationAttempt();
-    }
-
-    /**
-     * Provides numeric identifier for Java objects to be exposed.
-     * These identifiers must not collide.
-     */
-    interface ObjectIdAllocator {
-        int getObjectId(Object object, Class<? extends Annotation> safeAnnotationClass);
-
-        Object getObjectById(int id);
-
-        void unrefObjectByObject(Object object);
-    }
-
-    /** Method which may not be called. */
-    private static final Method sGetClassMethod;
-
-    static {
-        try {
-            sGetClassMethod = Object.class.getMethod("getClass");
-        } catch (NoSuchMethodException e) {
-            // java.lang.Object#getClass should always exist.
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * The object to which invocations should be directed.
-     *
-     * The target object cannot be referred to strongly, because it may contain
-     * references which form an uncollectable cycle.
-     */
-    private final WeakReference<Object> mTarget;
-
-    /**
-     * Annotation required on all exposed methods.
-     * If null, no annotation is required.
-     * In practice, this is usually {@link android.webkit.JavascriptInterface}.
-     */
-    private final Class<? extends Annotation> mSafeAnnotationClass;
-
-    /**
-     * Allocates IDs for other Java objects.
-     *
-     * Cannot be held strongly, because it may (via the objects it holds) contain
-     * references which form an uncollectable cycle.
-     */
-    private final WeakReference<ObjectIdAllocator> mObjectIdAllocator;
-
-    /** Receives notification about events for auditing. */
-    private final Auditor mAuditor;
-
-    /** Callable methods, indexed by name. */
-    private final SortedMap<String, List<Method>> mMethods = new TreeMap<>();
-
-    /** If true, allows an object context's inspection when {@link #getMethods} is called. */
-    private final boolean mAllowInspection;
-
-    private boolean mNotifiedReleasedObject;
-
-    public static final short UNSIGNED_BYTE_MASK = 0xff;
-    public static final int UNSIGNED_SHORT_MASK = 0xffff;
-    public static final long UNSIGNED_INT_MASK = 0xffffffffL;
-
-    private static final Pattern sDoubleNumberPattern =
-            Pattern.compile(
-                    "^(-?[0-9]+)(\\.0+)? ( ( (?:\\.[0-9]*[1-9])? )0* ) ((?:e.*)?)$",
-                    Pattern.COMMENTS);
-
-    public RemoteObjectImpl(
-            Object target,
-            Class<? extends Annotation> safeAnnotationClass,
-            Auditor auditor,
-            ObjectIdAllocator objectIdAllocator,
-            boolean allowInspection) {
-        mTarget = new WeakReference<>(target);
-        mSafeAnnotationClass = safeAnnotationClass;
-        mAuditor = auditor;
-        mObjectIdAllocator = new WeakReference<>(objectIdAllocator);
-        mAllowInspection = allowInspection;
-        mNotifiedReleasedObject = false;
-
-        for (Method method : target.getClass().getMethods()) {
-            if (safeAnnotationClass != null && !method.isAnnotationPresent(safeAnnotationClass)) {
-                continue;
-            }
-
-            String methodName = method.getName();
-            List<Method> methodsWithName = mMethods.get(methodName);
-            if (methodsWithName == null) {
-                methodsWithName = new ArrayList<>(1);
-                mMethods.put(methodName, methodsWithName);
-            }
-            methodsWithName.add(method);
-        }
-    }
-
-    @Override
-    public void hasMethod(String name, HasMethod_Response callback) {
-        callback.call(mMethods.containsKey(name));
-    }
-
-    @Override
-    public void getMethods(GetMethods_Response callback) {
-        if (!mAllowInspection) {
-            callback.call(new String[0]);
-            return;
-        }
-        Set<String> methodNames = mMethods.keySet();
-        callback.call(methodNames.toArray(new String[methodNames.size()]));
-    }
-
-    @Override
-    public void invokeMethod(
-            String name, RemoteInvocationArgument[] arguments, InvokeMethod_Response callback) {
-        Object target = mTarget.get();
-        ObjectIdAllocator objectIdAllocator = mObjectIdAllocator.get();
-        if (target == null || objectIdAllocator == null) {
-            // TODO(jbroman): Handle this.
-            return;
-        }
-
-        int numArguments = arguments.length;
-        Method method = findMethod(name, numArguments);
-        if (method == null) {
-            callback.call(makeErrorResult(RemoteInvocationError.METHOD_NOT_FOUND));
-            return;
-        }
-        if (method.equals(sGetClassMethod)) {
-            if (mAuditor != null) {
-                mAuditor.onObjectGetClassInvocationAttempt();
-            }
-            callback.call(makeErrorResult(RemoteInvocationError.OBJECT_GET_CLASS_BLOCKED));
-            return;
-        }
-        if (method.getReturnType().isArray()) {
-            // LIVECONNECT_COMPLIANCE: Existing behavior is to not call methods that
-            // return arrays. Spec requires calling the method and converting the
-            // result to a JavaScript array.
-            RemoteInvocationResult result = new RemoteInvocationResult();
-            result.value = new RemoteInvocationResultValue();
-            result.value.setSingletonValue(SingletonJavaScriptValue.UNDEFINED);
-            callback.call(result);
-            return;
-        }
-
-        Class<?>[] parameterTypes = method.getParameterTypes();
-        Object[] args = new Object[numArguments];
-        for (int i = 0; i < numArguments; i++) {
-            try {
-                args[i] =
-                        convertArgument(
-                                arguments[i],
-                                parameterTypes[i],
-                                StringCoercionMode.COERCE,
-                                objectIdAllocator);
-            } catch (IllegalArgumentException e) {
-                callback.call(makeErrorResult(RemoteInvocationError.NON_ASSIGNABLE_TYPES));
-                return;
-            }
-        }
-
-        Object result = null;
-        try {
-            result = method.invoke(target, args);
-        } catch (IllegalAccessException | IllegalArgumentException | NullPointerException e) {
-            // These should never happen.
-            //
-            // IllegalAccessException:
-            //   java.lang.Class#getMethods returns only public members, so |mMethods| should never
-            //   contain any method for which IllegalAccessException would be thrown.
-            //
-            // IllegalArgumentException:
-            //   Argument coercion logic is responsible for creating objects of a suitable Java
-            //   type.
-            //
-            // NullPointerException:
-            //   A user of this class is responsible for ensuring that the target is not collected.
-            throw new RuntimeException(e);
-        } catch (InvocationTargetException e) {
-            e.getCause().printStackTrace();
-            callback.call(makeErrorResult(RemoteInvocationError.EXCEPTION_THROWN));
-            return;
-        }
-
-        RemoteInvocationResult mojoResult =
-                convertResult(
-                        result, method.getReturnType(), objectIdAllocator, mSafeAnnotationClass);
-        callback.call(mojoResult);
-    }
-
-    @Override
-    public void notifyReleasedObject() {
-        mNotifiedReleasedObject = true;
-    }
-
-    @Override
-    public void close() {
-        Object target = mTarget.get();
-        ObjectIdAllocator objectIdAllocator = mObjectIdAllocator.get();
-        // If |mNotifiedReleasedObject| is false, this outlives the RemoteObjectHost and the object
-        // is not unreferenced by the RemoteObjectHost. So, we should unreference the object when
-        // the mojo pipe is closed.
-        if (target != null && objectIdAllocator != null && !mNotifiedReleasedObject) {
-            objectIdAllocator.unrefObjectByObject(target);
-        }
-
-        mTarget.clear();
-    }
-
-    @Override
-    public void onConnectionError(MojoException e) {
-        close();
-    }
-
-    private Method findMethod(String name, int numParameters) {
-        List<Method> methods = mMethods.get(name);
-        if (methods == null) {
-            return null;
-        }
-
-        // LIVECONNECT_COMPLIANCE: We just take the first method with the correct
-        // number of arguments, while the spec proposes using cost-based algorithm:
-        // https://jdk6.java.net/plugin2/liveconnect/#OVERLOADED_METHODS
-        for (Method method : methods) {
-            if (method.getParameterTypes().length == numParameters) return method;
-        }
-
-        return null;
-    }
-
-    @IntDef({StringCoercionMode.DO_NOT_COERCE, StringCoercionMode.COERCE})
-    @Retention(RetentionPolicy.SOURCE)
-    private @interface StringCoercionMode {
-        // Do not coerce non-strings to string; instead produce null.
-        // Used when coercing arguments inside arrays.
-        int DO_NOT_COERCE = 0;
-
-        // Coerce into strings more aggressively. Applied when the parameter type is
-        // java.lang.String exactly.
-        int COERCE = 1;
-    }
-
-    private static Object convertPrimitiveArrayElement(Number number, Class<?> parameterType) {
-        assert (parameterType.isPrimitive() && parameterType != boolean.class);
-        if (parameterType == byte.class) {
-            return number.byteValue();
-        } else if (parameterType == char.class) {
-            return (char) (number.intValue() & UNSIGNED_SHORT_MASK);
-        } else if (parameterType == short.class) {
-            return number.shortValue();
-        } else if (parameterType == int.class) {
-            return number.intValue();
-        } else if (parameterType == long.class) {
-            return number.longValue();
-        } else if (parameterType == float.class) {
-            return number.floatValue();
-        }
-
-        return number.doubleValue();
-    }
-
-    private abstract static class WrapBuffer {
-        protected Class<?> mParameterType;
-        protected int mLength;
-
-        WrapBuffer(Class<?> parameterType) {
-            mParameterType = parameterType;
-        }
-
-        public Object copyArray() {
-            if (mParameterType == boolean.class) {
-                // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec
-                // requires converting to false for 0 or NaN, true otherwise.
-                // The default value of the boolean elements in a boolean array is false.
-                return new boolean[mLength];
-            } else if (isFloatType() && mParameterType == char.class) {
-                // LIVECONNECT_COMPLIANCE: Existing behavior is to convert floating-point types to
-                // 0. Spec requires converting doubles similarly to how we convert floating-point
-                // types to other numeric types.
-                // The default value of the char elements in a char array is 0.
-                return new char[mLength];
-            } else if (mParameterType == String.class) {
-                // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null for all.
-                // The default value of the String elements in a String array is null.
-                return new String[mLength];
-            }
-
-            Object result = Array.newInstance(mParameterType, mLength);
-            for (int i = 0; i < mLength; i++) {
-                Array.set(result, i, convertPrimitiveArrayElement(get(i), mParameterType));
-            }
-            return result;
-        }
-
-        protected abstract Number get(int index);
-
-        protected boolean isFloatType() {
-            return false;
-        }
-    }
-
-    private static class WrapByteBuffer extends WrapBuffer {
-        ByteBuffer mBuffer;
-        boolean mUnsigned;
-
-        WrapByteBuffer(ByteBuffer buffer, Class<?> parameterType, boolean unsigned) {
-            super(parameterType);
-            mBuffer = buffer;
-            mLength = mBuffer.limit();
-            mUnsigned = unsigned;
-        }
-
-        @Override
-        public Object copyArray() {
-            if (mParameterType != byte.class) {
-                return super.copyArray();
-            }
-            byte[] result = new byte[mLength];
-            mBuffer.get(result);
-            return result;
-        }
-
-        @Override
-        protected Number get(int index) {
-            byte number = mBuffer.get(index);
-            return (mUnsigned ? (short) (number & UNSIGNED_BYTE_MASK) : number);
-        }
-    }
-
-    private static class WrapShortBuffer extends WrapBuffer {
-        ShortBuffer mBuffer;
-        boolean mUnsigned;
-
-        WrapShortBuffer(ShortBuffer buffer, Class<?> parameterType, boolean unsigned) {
-            super(parameterType);
-            mBuffer = buffer;
-            mLength = mBuffer.limit();
-            mUnsigned = unsigned;
-        }
-
-        @Override
-        public Object copyArray() {
-            if (mParameterType != short.class) {
-                return super.copyArray();
-            }
-            short[] result = new short[mLength];
-            mBuffer.get(result);
-            return result;
-        }
-
-        @Override
-        protected Number get(int index) {
-            short number = mBuffer.get(index);
-            return (mUnsigned ? (int) (number & UNSIGNED_SHORT_MASK) : number);
-        }
-    }
-
-    private static class WrapIntBuffer extends WrapBuffer {
-        IntBuffer mBuffer;
-        boolean mUnsigned;
-
-        WrapIntBuffer(IntBuffer buffer, Class<?> parameterType, boolean unsigned) {
-            super(parameterType);
-            mBuffer = buffer;
-            mLength = mBuffer.limit();
-            mUnsigned = unsigned;
-        }
-
-        @Override
-        public Object copyArray() {
-            if (mParameterType != int.class) {
-                return super.copyArray();
-            }
-            int[] result = new int[mLength];
-            mBuffer.get(result);
-            return result;
-        }
-
-        @Override
-        protected Number get(int index) {
-            int number = mBuffer.get(index);
-            return (mUnsigned ? (long) (number & UNSIGNED_INT_MASK) : number);
-        }
-    }
-
-    private static class WrapFloatBuffer extends WrapBuffer {
-        FloatBuffer mBuffer;
-
-        WrapFloatBuffer(FloatBuffer buffer, Class<?> parameterType) {
-            super(parameterType);
-            mBuffer = buffer;
-            mLength = mBuffer.limit();
-        }
-
-        @Override
-        public Object copyArray() {
-            if (mParameterType != float.class) {
-                return super.copyArray();
-            }
-            float[] result = new float[mLength];
-            mBuffer.get(result);
-            return result;
-        }
-
-        @Override
-        protected Number get(int index) {
-            return mBuffer.get(index);
-        }
-
-        @Override
-        protected boolean isFloatType() {
-            return true;
-        }
-    }
-
-    private static class WrapDoubleBuffer extends WrapBuffer {
-        DoubleBuffer mBuffer;
-
-        WrapDoubleBuffer(DoubleBuffer buffer, Class<?> parameterType) {
-            super(parameterType);
-            mBuffer = buffer;
-            mLength = mBuffer.limit();
-        }
-
-        @Override
-        public Object copyArray() {
-            if (mParameterType != double.class) {
-                return super.copyArray();
-            }
-            double[] result = new double[mLength];
-            mBuffer.get(result);
-            return result;
-        }
-
-        @Override
-        protected Number get(int index) {
-            return mBuffer.get(index);
-        }
-
-        @Override
-        protected boolean isFloatType() {
-            return true;
-        }
-    }
-
-    private static Object convertArgument(
-            RemoteInvocationArgument argument,
-            Class<?> parameterType,
-            @StringCoercionMode int stringCoercionMode,
-            ObjectIdAllocator objectIdAllocator) {
-        switch (argument.which()) {
-            case RemoteInvocationArgument.Tag.NumberValue:
-                // See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES.
-                // For conversion to numeric types, we need to replicate Java's type
-                // conversion rules.
-                double numberValue = argument.getNumberValue();
-                if (parameterType == byte.class) {
-                    return (byte) numberValue;
-                } else if (parameterType == char.class) {
-                    if (isInt32(numberValue)) {
-                        return (char) numberValue;
-                    } else {
-                        // LIVECONNECT_COMPLIANCE: Existing behavior is to convert double to 0.
-                        // Spec requires converting doubles similarly to how we convert doubles to
-                        // other numeric types.
-                        return (char) 0;
-                    }
-                } else if (parameterType == short.class) {
-                    return (short) numberValue;
-                } else if (parameterType == int.class) {
-                    return (int) numberValue;
-                } else if (parameterType == long.class) {
-                    return (long) numberValue;
-                } else if (parameterType == float.class) {
-                    return (float) numberValue;
-                } else if (parameterType == double.class) {
-                    return numberValue;
-                } else if (parameterType == boolean.class) {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec
-                    // requires converting to false for 0 or NaN, true otherwise.
-                    return false;
-                } else if (parameterType == String.class) {
-                    return stringCoercionMode == StringCoercionMode.COERCE
-                            ? doubleToString(numberValue)
-                            : null;
-                } else if (parameterType.isArray()) {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec
-                    // requires raising a JavaScript exception.
-                    return null;
-                } else {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec
-                    // requires handling object equivalents of primitive types.
-                    assert !parameterType.isPrimitive();
-                    return null;
-                }
-            case RemoteInvocationArgument.Tag.BooleanValue:
-                // See http://jdk6.java.net/plugin2/liveconnect/#JS_BOOLEAN_VALUES.
-                boolean booleanValue = argument.getBooleanValue();
-                if (parameterType == boolean.class) {
-                    return booleanValue;
-                } else if (parameterType.isPrimitive()) {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0 for all
-                    // non-boolean primitive types. Spec requires converting to 0 or 1.
-                    return getPrimitiveZero(parameterType);
-                } else if (parameterType == String.class) {
-                    return stringCoercionMode == StringCoercionMode.COERCE
-                            ? Boolean.toString(booleanValue)
-                            : null;
-                } else if (parameterType.isArray()) {
-                    return null;
-                } else {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
-                    // requires handling java.lang.Boolean and java.lang.Object.
-                    assert !parameterType.isPrimitive();
-                    return null;
-                }
-            case RemoteInvocationArgument.Tag.StringValue:
-                // See http://jdk6.java.net/plugin2/liveconnect/#JS_STRING_VALUES.
-                if (parameterType == String.class) {
-                    return mojoStringToJavaString(argument.getStringValue());
-                } else if (parameterType.isPrimitive()) {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec
-                    // requires using valueOf() method of corresponding object type, or
-                    // converting to boolean based on whether the string is empty.
-                    return getPrimitiveZero(parameterType);
-                } else if (parameterType.isArray()) {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
-                    // requires raising a JavaScript exception.
-                    return null;
-                } else {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
-                    // requires handling java.lang.Object.
-                    return null;
-                }
-            case RemoteInvocationArgument.Tag.SingletonValue:
-                int singletonValue = argument.getSingletonValue();
-                boolean isUndefined = singletonValue == SingletonJavaScriptValue.UNDEFINED;
-                if (parameterType == String.class) {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert undefined to
-                    // "undefined". Spec requires converting undefined to NULL.
-                    return (argument.getSingletonValue() == SingletonJavaScriptValue.UNDEFINED
-                                    && stringCoercionMode == StringCoercionMode.COERCE)
-                            ? "undefined"
-                            : null;
-                } else if (parameterType.isPrimitive()) {
-                    return getPrimitiveZero(parameterType);
-                } else if (parameterType.isArray()) {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
-                    // requires raising a JavaScript exception.
-                    return null;
-                } else {
-                    return null;
-                }
-            case RemoteInvocationArgument.Tag.ArrayValue:
-                RemoteInvocationArgument[] arrayValue = argument.getArrayValue();
-                if (parameterType.isArray()) {
-                    Class<?> componentType = parameterType.getComponentType();
-
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for
-                    // multi-dimensional and object arrays. Spec requires handling them.
-                    if (!componentType.isPrimitive() && componentType != String.class) {
-                        return null;
-                    }
-
-                    Object result = Array.newInstance(componentType, arrayValue.length);
-                    for (int i = 0; i < arrayValue.length; i++) {
-                        Object element =
-                                convertArgument(
-                                        arrayValue[i],
-                                        componentType,
-                                        StringCoercionMode.DO_NOT_COERCE,
-                                        objectIdAllocator);
-                        Array.set(result, i, element);
-                    }
-                    return result;
-                } else if (parameterType == String.class) {
-                    return stringCoercionMode == StringCoercionMode.COERCE ? "undefined" : null;
-                } else if (parameterType.isPrimitive()) {
-                    return getPrimitiveZero(parameterType);
-                } else {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec requires
-                    // converting if the target type is netscape.javascript.JSObject, otherwise
-                    // raising a JavaScript exception.
-                    return null;
-                }
-            case RemoteInvocationArgument.Tag.TypedArrayValue:
-                RemoteTypedArray typedArrayValue = argument.getTypedArrayValue();
-                if (parameterType.isArray()) {
-                    Class<?> componentType = parameterType.getComponentType();
-
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for
-                    // multi-dimensional and object arrays. Spec requires handling them.
-                    if (!componentType.isPrimitive() && componentType != String.class) {
-                        return null;
-                    } else if (componentType.isArray()) {
-                        // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
-                        // requires raising a JavaScript exception.
-                        return null;
-                    }
-
-                    // TODO(crbug.com/40554401): Remove unnecessary copy for the performance.
-                    ByteBuffer typedBuffer =
-                            ByteBuffer.wrap(
-                                    BigBufferUtil.getBytesFromBigBuffer(typedArrayValue.buffer));
-                    typedBuffer.order(ByteOrder.nativeOrder());
-
-                    if (typedArrayValue.type == RemoteArrayType.INT8_ARRAY) {
-                        return new WrapByteBuffer(typedBuffer, componentType, false).copyArray();
-                    } else if (typedArrayValue.type == RemoteArrayType.UINT8_ARRAY) {
-                        return new WrapByteBuffer(typedBuffer, componentType, true).copyArray();
-                    } else if (typedArrayValue.type == RemoteArrayType.INT16_ARRAY) {
-                        return new WrapShortBuffer(
-                                        typedBuffer.asShortBuffer(), componentType, false)
-                                .copyArray();
-                    } else if (typedArrayValue.type == RemoteArrayType.UINT16_ARRAY) {
-                        return new WrapShortBuffer(typedBuffer.asShortBuffer(), componentType, true)
-                                .copyArray();
-                    } else if (typedArrayValue.type == RemoteArrayType.INT32_ARRAY) {
-                        return new WrapIntBuffer(typedBuffer.asIntBuffer(), componentType, false)
-                                .copyArray();
-                    } else if (typedArrayValue.type == RemoteArrayType.UINT32_ARRAY) {
-                        return new WrapIntBuffer(typedBuffer.asIntBuffer(), componentType, true)
-                                .copyArray();
-                    } else if (typedArrayValue.type == RemoteArrayType.FLOAT32_ARRAY) {
-                        return new WrapFloatBuffer(typedBuffer.asFloatBuffer(), componentType)
-                                .copyArray();
-                    } else if (typedArrayValue.type == RemoteArrayType.FLOAT64_ARRAY) {
-                        return new WrapDoubleBuffer(typedBuffer.asDoubleBuffer(), componentType)
-                                .copyArray();
-                    } else {
-                        return null;
-                    }
-                } else if (parameterType == String.class) {
-                    return stringCoercionMode == StringCoercionMode.COERCE ? "undefined" : null;
-                } else if (parameterType.isPrimitive()) {
-                    return getPrimitiveZero(parameterType);
-                } else {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec requires
-                    // converting if the target type is netscape.javascript.JSObject, otherwise
-                    // raising a JavaScript exception.
-                    return null;
-                }
-            case RemoteInvocationArgument.Tag.ObjectIdValue:
-                if (parameterType == String.class) {
-                    return stringCoercionMode == StringCoercionMode.COERCE ? "undefined" : null;
-                } else if (parameterType.isPrimitive()) {
-                    return getPrimitiveZero(parameterType);
-                } else if (parameterType.isArray()) {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec
-                    // requires raising a JavaScript exception.
-                    return null;
-                }
-
-                Object object = objectIdAllocator.getObjectById(argument.getObjectIdValue());
-                if (object == null) {
-                    // LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec
-                    // requires converting if the target type is
-                    // netscape.javascript.JSObject, otherwise raising a JavaScript
-                    // exception.
-                    return null;
-                }
-                if (parameterType.isInstance(object)) return object;
-
-                throw new IllegalArgumentException("incompatible argument type with object id");
-            default:
-                throw new RuntimeException("invalid wire argument type");
-        }
-    }
-
-    private static RemoteInvocationResult convertResult(
-            Object result,
-            Class<?> returnType,
-            ObjectIdAllocator objectIdAllocator,
-            Class<? extends Annotation> safeAnnotationClass) {
-        // Methods returning arrays should not be called (for legacy reasons).
-        assert !returnType.isArray();
-
-        // LIVECONNECT_COMPLIANCE: The specification suggests that the conversion should happen
-        // based on the type of the result value. Existing behavior is to rely on the declared
-        // return type of the method. This means, for instance, that a java.lang.String returned
-        // from a method declared as returning java.lang.Object will not be converted to a
-        // JavaScript string.
-        RemoteInvocationResultValue resultValue = new RemoteInvocationResultValue();
-        if (returnType == void.class) {
-            resultValue.setSingletonValue(SingletonJavaScriptValue.UNDEFINED);
-        } else if (returnType == boolean.class) {
-            resultValue.setBooleanValue((Boolean) result);
-        } else if (returnType == char.class) {
-            resultValue.setNumberValue((Character) result);
-        } else if (returnType.isPrimitive()) {
-            resultValue.setNumberValue(((Number) result).doubleValue());
-        } else if (returnType == String.class) {
-            if (result == null) {
-                // LIVECONNECT_COMPLIANCE: Existing behavior is to return undefined.
-                // Spec requires returning a null string.
-                resultValue.setSingletonValue(SingletonJavaScriptValue.UNDEFINED);
-            } else {
-                resultValue.setStringValue(javaStringToMojoString((String) result));
-            }
-        } else if (result == null) {
-            resultValue.setSingletonValue(SingletonJavaScriptValue.NULL);
-        } else {
-            int objectId = objectIdAllocator.getObjectId(result, safeAnnotationClass);
-            resultValue.setObjectId(objectId);
-        }
-        RemoteInvocationResult mojoResult = new RemoteInvocationResult();
-        mojoResult.value = resultValue;
-        return mojoResult;
-    }
-
-    private static RemoteInvocationResult makeErrorResult(int error) {
-        assert error != RemoteInvocationError.OK;
-        RemoteInvocationResult result = new RemoteInvocationResult();
-        result.error = error;
-        return result;
-    }
-
-    /**
-     * Returns whether the value is an Int32 in the V8 API sense.
-     * That is, it has an integer value in [-2^31, 2^31) and is not negative zero.
-     */
-    private static boolean isInt32(double doubleValue) {
-        return doubleValue % 1.0 == 0.0
-                && doubleValue >= Integer.MIN_VALUE
-                && doubleValue <= Integer.MAX_VALUE
-                && (doubleValue != 0.0 || (1.0 / doubleValue) > 0.0);
-    }
-
-    private static String doubleToString(double doubleValue) {
-        // For compatibility, imitate the existing behavior.
-        // The previous implementation applied Int64ToString to any integer that fit in 32 bits,
-        // except for negative zero, and base::StringPrintf("%.6lg", doubleValue) for all other
-        // values.
-        if (Double.isNaN(doubleValue)) {
-            return "nan";
-        }
-        if (Double.isInfinite(doubleValue)) {
-            return doubleValue > 0 ? "inf" : "-inf";
-        }
-        // Negative zero is mathematically an integer, but keeps its negative sign.
-        if (doubleValue == 0.0 && (1.0 / doubleValue) < 0.0) {
-            return "-0";
-        }
-        // All other 32-bit signed integers are formatted without abbreviation.
-        if (doubleValue % 1.0 == 0.0
-                && doubleValue >= Integer.MIN_VALUE
-                && doubleValue <= Integer.MAX_VALUE) {
-            return Integer.toString((int) doubleValue);
-        }
-        // Remove trailing zeroes and, if appropriate, the decimal point.
-        // Expression is somewhat complicated, in order to deal with scientific notation. Either
-        // group 2 will match (and so the decimal will be stripped along with zeroes), or group 3
-        // will match (and the decimal will be left), but not both (since there cannot be more than
-        // one decimal point). Group 5 will match an exponential part.
-        return sDoubleNumberPattern
-                .matcher(String.format((Locale) null, "%.6g", doubleValue))
-                .replaceAll("$1$4$5");
-    }
-
-    private static Object getPrimitiveZero(Class<?> parameterType) {
-        assert parameterType.isPrimitive();
-        if (parameterType == boolean.class) {
-            return false;
-        } else if (parameterType == byte.class) {
-            return (byte) 0;
-        } else if (parameterType == char.class) {
-            return (char) 0;
-        } else if (parameterType == short.class) {
-            return (short) 0;
-        } else if (parameterType == int.class) {
-            return (int) 0;
-        } else if (parameterType == long.class) {
-            return (long) 0;
-        } else if (parameterType == float.class) {
-            return (float) 0;
-        } else if (parameterType == double.class) {
-            return (double) 0;
-        } else {
-            throw new RuntimeException("unexpected primitive type " + parameterType);
-        }
-    }
-
-    private static String mojoStringToJavaString(String16 mojoString) {
-        short[] data = mojoString.data;
-        char[] chars = new char[data.length];
-        for (int i = 0; i < chars.length; i++) {
-            chars[i] = (char) data[i];
-        }
-        return String.valueOf(chars);
-    }
-
-    private static String16 javaStringToMojoString(String string) {
-        short[] data = new short[string.length()];
-        for (int i = 0; i < data.length; i++) {
-            data[i] = (short) string.charAt(i);
-        }
-        String16 mojoString = new String16();
-        mojoString.data = data;
-        return mojoString;
-    }
-}
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectInjector.java b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectInjector.java
deleted file mode 100644
index dae12b6..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectInjector.java
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright 2020 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.content.browser.remoteobjects;
-
-import org.chromium.blink.mojom.RemoteObjectGateway;
-import org.chromium.blink.mojom.RemoteObjectGatewayFactory;
-import org.chromium.content.browser.webcontents.WebContentsImpl;
-import org.chromium.content_public.browser.GlobalRenderFrameHostId;
-import org.chromium.content_public.browser.RenderFrameHost;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.WebContentsObserver;
-import org.chromium.mojo.bindings.InterfaceRequest;
-import org.chromium.mojo.system.Pair;
-import org.chromium.mojo.system.impl.CoreImpl;
-
-import java.lang.annotation.Annotation;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Owned and constructed by JavascriptInjectorImpl.
- * Could possibly be flattened into JavascriptInjectorImpl eventually, but may be nice to keep
- * separate while there are separate codepaths.
- */
-public final class RemoteObjectInjector extends WebContentsObserver {
-    /**
-     * Helper class for holding objects used to interact
-     * with the RemoteObjectGateway in the renderer.
-     */
-    private static class RemoteObjectGatewayHelper {
-        public RemoteObjectGateway.Proxy gateway;
-        public RemoteObjectHostImpl host;
-        public RemoteObjectRegistry registry;
-
-        public RemoteObjectGatewayHelper(
-                RemoteObjectGateway.Proxy newGateway,
-                RemoteObjectHostImpl newHost,
-                RemoteObjectRegistry newRegistry) {
-            gateway = newGateway;
-            host = newHost;
-            registry = newRegistry;
-        }
-    }
-
-    private final Set<Object> mRetainingSet = new HashSet<>();
-    private final Map<String, Pair<Object, Class<? extends Annotation>>> mInjectedObjects =
-            new HashMap<>();
-    // TODO(crbug.com/40756645): This is essentially implementing DocumentUserData. Once a java
-    // equivalent of that is created, we should use it instead of managing RFH associated state
-    // here.
-    private final Map<GlobalRenderFrameHostId, RemoteObjectGatewayHelper>
-            mRemoteObjectGatewayHelpers = new HashMap<>();
-    private boolean mAllowInspection = true;
-
-    public RemoteObjectInjector(WebContents webContents) {
-        super(webContents);
-    }
-
-    @Override
-    public void renderFrameCreated(GlobalRenderFrameHostId id) {
-        if (mInjectedObjects.isEmpty()) return;
-
-        WebContents webContents = mWebContents.get();
-        if (webContents == null) return;
-
-        RenderFrameHost frameHost = webContents.getRenderFrameHostFromId(id);
-        if (frameHost == null) return;
-
-        for (Map.Entry<String, Pair<Object, Class<? extends Annotation>>> entry :
-                mInjectedObjects.entrySet()) {
-            addInterfaceForFrame(
-                    frameHost, entry.getKey(), entry.getValue().first, entry.getValue().second);
-        }
-    }
-
-    @Override
-    public void renderFrameDeleted(GlobalRenderFrameHostId id) {
-        mRemoteObjectGatewayHelpers.remove(id);
-    }
-
-    public void addInterface(
-            Object object, String name, Class<? extends Annotation> requiredAnnotation) {
-        WebContentsImpl webContents = (WebContentsImpl) mWebContents.get();
-        if (webContents == null) return;
-
-        Pair<Object, Class<? extends Annotation>> value = mInjectedObjects.get(name);
-
-        // Nothing to do if the named object already exists.
-        if (value != null && value.first == object) return;
-
-        if (value != null) {
-            // Remove existing name for replacement.
-            removeInterface(name);
-        }
-
-        mInjectedObjects.put(name, new Pair<>(object, requiredAnnotation));
-
-        List<RenderFrameHost> frames = webContents.getAllRenderFrameHosts();
-        for (RenderFrameHost frame : frames) {
-            // If there's no renderer frame yet, we will add the interface when
-            // it is created.
-            if (frame.isRenderFrameLive()) {
-                addInterfaceForFrame(frame, name, object, requiredAnnotation);
-            }
-        }
-    }
-
-    public void removeInterface(String name) {
-        WebContentsImpl webContents = (WebContentsImpl) mWebContents.get();
-        if (webContents == null) return;
-
-        Pair<Object, Class<? extends Annotation>> value = mInjectedObjects.remove(name);
-        if (value == null) return;
-
-        List<RenderFrameHost> frames = webContents.getAllRenderFrameHosts();
-        for (RenderFrameHost frame : frames) {
-            removeInterfaceForFrame(frame, name, value.first);
-        }
-    }
-
-    public void setAllowInspection(boolean allow) {
-        WebContentsImpl webContents = (WebContentsImpl) mWebContents.get();
-        if (webContents == null) return;
-
-        mAllowInspection = allow;
-
-        List<RenderFrameHost> frames = webContents.getAllRenderFrameHosts();
-        for (RenderFrameHost frame : frames) {
-            setAllowInspectionForFrame(frame);
-        }
-    }
-
-    private void addInterfaceForFrame(
-            RenderFrameHost frameHost,
-            String name,
-            Object object,
-            Class<? extends Annotation> requiredAnnotation) {
-        RemoteObjectGatewayHelper helper = getRemoteObjectGatewayHelperForFrame(frameHost);
-        helper.gateway.addNamedObject(
-                name, helper.registry.getObjectId(object, requiredAnnotation));
-    }
-
-    private void removeInterfaceForFrame(RenderFrameHost frameHost, String name, Object object) {
-        RemoteObjectGatewayHelper helper =
-                mRemoteObjectGatewayHelpers.get(frameHost.getGlobalRenderFrameHostId());
-        if (helper == null) return;
-
-        helper.gateway.removeNamedObject(name);
-        helper.registry.unrefObjectByObject(object);
-    }
-
-    private void setAllowInspectionForFrame(RenderFrameHost frameHost) {
-        RemoteObjectGatewayHelper helper =
-                mRemoteObjectGatewayHelpers.get(frameHost.getGlobalRenderFrameHostId());
-        if (helper == null) return;
-
-        helper.host.setAllowInspection(mAllowInspection);
-    }
-
-    private RemoteObjectGatewayHelper getRemoteObjectGatewayHelperForFrame(
-            RenderFrameHost frameHost) {
-        GlobalRenderFrameHostId frameHostId = frameHost.getGlobalRenderFrameHostId();
-        // Only create one instance of RemoteObjectHostImpl per frame and store it in a map so it is
-        // reused in future calls.
-        if (!mRemoteObjectGatewayHelpers.containsKey(frameHostId)) {
-            RemoteObjectRegistry registry = new RemoteObjectRegistry(mRetainingSet);
-
-            // Construct a RemoteObjectHost implementation.
-            RemoteObjectHostImpl host =
-                    new RemoteObjectHostImpl(
-                            new RemoteObjectAuditorImpl(), registry, mAllowInspection);
-
-            RemoteObjectGatewayFactory factory =
-                    frameHost.getInterfaceToRendererFrame(RemoteObjectGatewayFactory.MANAGER);
-
-            Pair<RemoteObjectGateway.Proxy, InterfaceRequest<RemoteObjectGateway>> result =
-                    RemoteObjectGateway.MANAGER.getInterfaceRequest(CoreImpl.getInstance());
-            factory.createRemoteObjectGateway(host, result.second);
-            mRemoteObjectGatewayHelpers.put(
-                    frameHostId, new RemoteObjectGatewayHelper(result.first, host, registry));
-        }
-
-        return mRemoteObjectGatewayHelpers.get(frameHostId);
-    }
-}
diff --git a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectRegistry.java b/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectRegistry.java
deleted file mode 100644
index 43b0f9f..0000000
--- a/content/public/android/java/src/org/chromium/content/browser/remoteobjects/RemoteObjectRegistry.java
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser.remoteobjects;
-
-import android.util.SparseArray;
-
-import java.lang.annotation.Annotation;
-import java.util.IdentityHashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Owns a set of objects on behalf of RemoteObjectHost's client.
- *
- * These objects could contain references which would keep the WebContents alive
- * longer than expected, and so must not be held alive by any other GC root.
- *
- * The object's reference count is changed in the following cases:
- *   - When the wrapper object is created in the renderer, it is increased.
- *   - When the wrapper object is destroyed in the renderer, it is decreased via RemoteObjectHost
- *     in general. If the wrapper object, RemoteObject, outlives RemoteObjectHost, the object could
- *     not drop the reference via RemoteObjectHost. By explicitly notifying {@link RemoteObjectImpl}
- *     that the object has been released, it ensures that the object's reference is released by
- *     {@link RemoteObjectImpl} when the Mojo pipe is closed.
- *   - When the object is named in Java code, it is increased.
- *   - When the object is removed from the named objects in Java code, it is decreased.
- *
- */
-final class RemoteObjectRegistry implements RemoteObjectImpl.ObjectIdAllocator {
-    private final Set<? super RemoteObjectRegistry> mRetainingSet;
-
-    private static class Entry {
-        Entry(int id, Object object, Class<? extends Annotation> safeAnnotationClass) {
-            this.id = id;
-            this.object = object;
-            this.safeAnnotationClass = safeAnnotationClass;
-        }
-
-        // The ID assigned to the object, which can be used by the renderer
-        // to refer to it.
-        public final int id;
-
-        // The injected object itself
-        public final Object object;
-
-        // The number of outstanding references to this object.
-        // This includes:
-        //   * wrapper objects in the renderer (removed when it closes the pipe)
-        //   * names assigned to the object by developer Java code
-        public int referenceCount = 1;
-
-        // The annotation class annotated to the object, which can be exposed
-        // to JavaScript code.
-        public Class<? extends Annotation> safeAnnotationClass;
-    }
-
-    private final SparseArray<Entry> mEntriesById = new SparseArray<>();
-    private final Map<Object, Entry> mEntriesByObject = new IdentityHashMap<>();
-    private int mNextId;
-
-    RemoteObjectRegistry(Set<? super RemoteObjectRegistry> retainingSet) {
-        retainingSet.add(this);
-        mRetainingSet = retainingSet;
-    }
-
-    public void close() {
-        boolean removed = mRetainingSet.remove(this);
-        assert removed;
-    }
-
-    public synchronized Class<? extends Annotation> getSafeAnnotationClass(Object object) {
-        Entry entry = mEntriesByObject.get(object);
-        return entry != null ? entry.safeAnnotationClass : null;
-    }
-
-    @Override
-    public synchronized int getObjectId(
-            Object object, Class<? extends Annotation> safeAnnotationClass) {
-        Entry entry = mEntriesByObject.get(object);
-        if (entry != null) {
-            entry.referenceCount++;
-            return entry.id;
-        }
-
-        int newId = mNextId++;
-        assert newId >= 0;
-        entry = new Entry(newId, object, safeAnnotationClass);
-        mEntriesById.put(newId, entry);
-        mEntriesByObject.put(object, entry);
-        return newId;
-    }
-
-    @Override
-    public synchronized Object getObjectById(int id) {
-        Entry entry = mEntriesById.get(id);
-        return entry != null ? entry.object : null;
-    }
-
-    @Override
-    public synchronized void unrefObjectByObject(Object object) {
-        unrefObject(mEntriesByObject.get(object));
-    }
-
-    public synchronized void refObjectById(int id) {
-        Entry entry = mEntriesById.get(id);
-        if (entry == null) return;
-
-        entry.referenceCount++;
-    }
-
-    public synchronized void unrefObjectById(int id) {
-        unrefObject(mEntriesById.get(id));
-    }
-
-    private synchronized void unrefObject(Entry entry) {
-        if (entry == null) return;
-
-        entry.referenceCount--;
-        assert entry.referenceCount >= 0;
-        if (entry.referenceCount > 0) return;
-
-        mEntriesById.remove(entry.id);
-        mEntriesByObject.remove(entry.object);
-    }
-}
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/JavascriptInjector.java b/content/public/android/java/src/org/chromium/content_public/browser/JavascriptInjector.java
index dfb9890..557c2a9 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/JavascriptInjector.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/JavascriptInjector.java
@@ -12,23 +12,22 @@
 import java.util.Map;
 
 /**
- * Interface that provides API used to inject user-defined objects that allow
- * custom Javascript interfaces.
+ * Interface that provides API used to inject user-defined objects that allow custom Javascript
+ * interfaces.
  */
 public interface JavascriptInjector {
     /**
      * @param webContents {@link WebContents} object.
-     * @param useMojo Whether to use {@link RemoteObjectInjector} methods
-     * @return {@link JavascriptInjector} object used for the give WebContents.
-     *         Creates one if not present.
+     * @return {@link JavascriptInjector} object used for the give WebContents. Creates one if not
+     *     present.
      */
-    static JavascriptInjector fromWebContents(WebContents webContents, boolean useMojo) {
-        return JavascriptInjectorImpl.fromWebContents(webContents, useMojo);
+    static JavascriptInjector fromWebContents(WebContents webContents) {
+        return JavascriptInjectorImpl.fromWebContents(webContents);
     }
 
     /**
-     * Returns Javascript interface objects previously injected via
-     * {@link #addPossiblyUnsafeInterface(Object, String)}.
+     * Returns Javascript interface objects previously injected via {@link
+     * #addPossiblyUnsafeInterface(Object, String)}.
      *
      * @return the mapping of names to interface objects and corresponding annotation classes
      */
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherHelperTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherHelperTest.java
index 315d59d9..cce9704 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherHelperTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherHelperTest.java
@@ -37,7 +37,6 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
-import org.chromium.content_public.browser.test.ChildProcessAllocatorSettings;
 import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_shell_apk.ChildProcessLauncherTestHelperService;
@@ -81,11 +80,10 @@
     @Test
     @MediumTest
     @Feature({"ProcessManagement"})
-    @ChildProcessAllocatorSettings(
-            sandboxedServiceCount = 2,
-            sandboxedServiceName = DEFAULT_SANDBOXED_PROCESS_SERVICE)
     @DisabledTest(message = "Flaky - crbug.com/752691")
     public void testBindServiceFromMultipleProcesses() throws RemoteException {
+        ChildProcessLauncherHelperImpl.setSandboxServicesSettingsForTesting(
+                /* factory= */ null, 2, DEFAULT_SANDBOXED_PROCESS_SERVICE);
         final Context context = InstrumentationRegistry.getTargetContext();
 
         // Start the Helper service.
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeActivityTestRule.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeActivityTestRule.java
index ce276265..2943b0d 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeActivityTestRule.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeActivityTestRule.java
@@ -11,8 +11,6 @@
 import org.junit.runners.model.Statement;
 
 import org.chromium.base.Log;
-import org.chromium.base.test.params.ParameterProvider;
-import org.chromium.base.test.params.ParameterSet;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.content_public.browser.JavascriptInjector;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -22,40 +20,13 @@
 import org.chromium.content_shell_apk.ContentShellActivityTestRule;
 
 import java.lang.annotation.Annotation;
-import java.util.Arrays;
-import java.util.List;
 
 /** ActivityTestRule with common functionality for testing the Java Bridge. */
 public class JavaBridgeActivityTestRule extends ContentShellActivityTestRule {
     /** Shared name for batched JavaBridge tests. */
     public static final String BATCH = "JavaBridgeActivityTestRule";
 
-    /** {@link ParameterProvider} used for parameterized test that provides the Mojo usage state. */
-    public static class MojoTestParams implements ParameterProvider {
-        private static List<ParameterSet> sMojoTestParams =
-                Arrays.asList(
-                        new ParameterSet().value(false).name("MojoUnused"),
-                        new ParameterSet().value(true).name("MojoUsed"));
-
-        @Override
-        public List<ParameterSet> getParameters() {
-            return sMojoTestParams;
-        }
-    }
-
-    /** {@link ParameterProvider} used for parameterized test that keeps the legacy tests. */
-    public static class LegacyTestParams implements ParameterProvider {
-        private static List<ParameterSet> sLegacyTestParams =
-                Arrays.asList(new ParameterSet().value(false));
-
-        @Override
-        public List<ParameterSet> getParameters() {
-            return sLegacyTestParams;
-        }
-    }
-
     private TestCallbackHelperContainer mTestCallbackHelperContainer;
-    private boolean mUseMojo;
 
     public static class Controller {
         private static final int RESULT_WAIT_TIME = 5000;
@@ -155,7 +126,7 @@
                         public void run() {
                             WebContents webContents = getWebContents();
                             JavascriptInjector injector =
-                                    JavascriptInjector.fromWebContents(webContents, mUseMojo);
+                                    JavascriptInjector.fromWebContents(webContents);
                             injector.addPossiblyUnsafeInterface(object1, name1, requiredAnnotation);
                             if (object2 != null && name2 != null) {
                                 injector.addPossiblyUnsafeInterface(
@@ -171,10 +142,6 @@
         }
     }
 
-    public void setupMojoTest(boolean useMojo) {
-        mUseMojo = useMojo;
-    }
-
     public void synchronousPageReload() throws Throwable {
         TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
                 mTestCallbackHelperContainer.getOnPageFinishedHelper();
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeArrayCoercionTest.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeArrayCoercionTest.java
index bba18087..8f8e67b 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeArrayCoercionTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeArrayCoercionTest.java
@@ -14,28 +14,21 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.params.BaseJUnit4RunnerDelegate;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content.browser.JavaBridgeActivityTestRule.Controller;
 
 /**
- * Part of the test suite for the Java Bridge. This class tests that we correctly convert
- * JavaScript arrays to Java arrays when passing them to the methods of injected Java objects.
+ * Part of the test suite for the Java Bridge. This class tests that we correctly convert JavaScript
+ * arrays to Java arrays when passing them to the methods of injected Java objects.
  *
- * The conversions should follow
- * http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS. Places in
- * which the implementation differs from the spec are marked with
- * LIVECONNECT_COMPLIANCE.
- * FIXME: Consider making our implementation more compliant, if it will not
- * break backwards-compatibility. See b/4408210.
+ * <p>The conversions should follow http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS.
+ * Places in which the implementation differs from the spec are marked with LIVECONNECT_COMPLIANCE.
+ * FIXME: Consider making our implementation more compliant, if it will not break
+ * backwards-compatibility. See b/4408210.
  */
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(BaseJUnit4RunnerDelegate.class)
+@RunWith(BaseJUnit4ClassRunner.class)
 @Batch(JavaBridgeActivityTestRule.BATCH)
 public class JavaBridgeArrayCoercionTest {
     private static final double ASSERTION_DELTA = 0;
@@ -198,11 +191,6 @@
     // Two custom types used when testing passing objects.
     private static class CustomType {}
 
-    @UseMethodParameterBefore(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void setupMojoTest(boolean useMojo) {
-        mActivityTestRule.setupMojoTest(useMojo);
-    }
-
     private TestObject mTestObject;
 
     @Before
@@ -219,8 +207,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNumberInt32(boolean useMojo) throws Throwable {
+    public void testPassNumberInt32() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setBooleanArray([0]);");
         Assert.assertFalse(mTestObject.waitForBooleanArray()[0]);
         // LIVECONNECT_COMPLIANCE: Should convert to boolean.
@@ -266,8 +253,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNumberDouble(boolean useMojo) throws Throwable {
+    public void testPassNumberDouble() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Should convert to boolean.
         mActivityTestRule.executeJavaScript("testObject.setBooleanArray([42.1]);");
         Assert.assertFalse(mTestObject.waitForBooleanArray()[0]);
@@ -312,8 +298,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNumberNaN(boolean useMojo) throws Throwable {
+    public void testPassNumberNaN() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setBooleanArray([Number.NaN]);");
         Assert.assertFalse(mTestObject.waitForBooleanArray()[0]);
 
@@ -356,8 +341,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNumberInfinity(boolean useMojo) throws Throwable {
+    public void testPassNumberInfinity() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setBooleanArray([Infinity]);");
         Assert.assertFalse(mTestObject.waitForBooleanArray()[0]);
 
@@ -403,8 +387,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassBoolean(boolean useMojo) throws Throwable {
+    public void testPassBoolean() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setBooleanArray([true]);");
         Assert.assertTrue(mTestObject.waitForBooleanArray()[0]);
         mActivityTestRule.executeJavaScript("testObject.setBooleanArray([false]);");
@@ -470,8 +453,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassString(boolean useMojo) throws Throwable {
+    public void testPassString() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Non-empty string should convert to true.
         mActivityTestRule.executeJavaScript("testObject.setBooleanArray([\"+042.10\"]);");
         Assert.assertFalse(mTestObject.waitForBooleanArray()[0]);
@@ -521,8 +503,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassJavaScriptObject(boolean useMojo) throws Throwable {
+    public void testPassJavaScriptObject() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Should raise a JavaScript exception.
         mActivityTestRule.executeJavaScript("testObject.setBooleanArray([{foo: 42}]);");
         Assert.assertFalse(mTestObject.waitForBooleanArray()[0]);
@@ -573,8 +554,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassJavaObject(boolean useMojo) throws Throwable {
+    public void testPassJavaObject() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Should raise a JavaScript exception.
         mActivityTestRule.executeJavaScript(
                 "testObject.setBooleanArray([testObject.getObjectInstance()]);");
@@ -639,8 +619,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNull(boolean useMojo) throws Throwable {
+    public void testPassNull() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setByteArray([null]);");
         Assert.assertEquals(0, mTestObject.waitForByteArray()[0]);
 
@@ -682,8 +661,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassUndefined(boolean useMojo) throws Throwable {
+    public void testPassUndefined() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setByteArray([undefined]);");
         Assert.assertEquals(0, mTestObject.waitForByteArray()[0]);
 
@@ -724,8 +702,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassInt8Array(boolean useMojo) throws Throwable {
+    public void testPassInt8Array() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(1);");
         mActivityTestRule.executeJavaScript("int8_array = new Int8Array(buffer);");
         mActivityTestRule.executeJavaScript("int8_array[0] = 42;");
@@ -768,8 +745,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testPassInt8ArrayWithNagativeValue(boolean useMojo) throws Throwable {
+    public void testPassInt8ArrayWithNagativeValue() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(1);");
         mActivityTestRule.executeJavaScript("int8_array = new Int8Array(buffer);");
         mActivityTestRule.executeJavaScript("int8_array[0] = -1;");
@@ -800,8 +776,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassUint8Array(boolean useMojo) throws Throwable {
+    public void testPassUint8Array() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(1);");
         mActivityTestRule.executeJavaScript("uint8_array = new Uint8Array(buffer);");
         mActivityTestRule.executeJavaScript("uint8_array[0] = 42;");
@@ -843,8 +818,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testPassUint8ArrayWithMaxValue(boolean useMojo) throws Throwable {
+    public void testPassUint8ArrayWithMaxValue() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(1);");
         mActivityTestRule.executeJavaScript("uint8_array = new Uint8Array(buffer);");
         mActivityTestRule.executeJavaScript("uint8_array[0] = 255;");
@@ -875,8 +849,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassInt16Array(boolean useMojo) throws Throwable {
+    public void testPassInt16Array() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(2);");
         mActivityTestRule.executeJavaScript("int16_array = new Int16Array(buffer);");
         mActivityTestRule.executeJavaScript("int16_array[0] = 42;");
@@ -919,8 +892,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassUint16Array(boolean useMojo) throws Throwable {
+    public void testPassUint16Array() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(2);");
         mActivityTestRule.executeJavaScript("uint16_array = new Uint16Array(buffer);");
         mActivityTestRule.executeJavaScript("uint16_array[0] = 42;");
@@ -963,8 +935,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassUint16ArrayWithMaxValue(boolean useMojo) throws Throwable {
+    public void testPassUint16ArrayWithMaxValue() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(2);");
         mActivityTestRule.executeJavaScript("uint16_array = new Uint16Array(buffer);");
         mActivityTestRule.executeJavaScript("uint16_array[0] = 65535;");
@@ -998,8 +969,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassInt32Array(boolean useMojo) throws Throwable {
+    public void testPassInt32Array() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(4);");
         mActivityTestRule.executeJavaScript("int32_array = new Int32Array(buffer);");
         mActivityTestRule.executeJavaScript("int32_array[0] = 42;");
@@ -1042,8 +1012,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassUint32Array(boolean useMojo) throws Throwable {
+    public void testPassUint32Array() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(4);");
         mActivityTestRule.executeJavaScript("uint32_array = new Uint32Array(buffer);");
         mActivityTestRule.executeJavaScript("uint32_array[0] = 42;");
@@ -1086,8 +1055,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassUint32ArrayWithMaxValue(boolean useMojo) throws Throwable {
+    public void testPassUint32ArrayWithMaxValue() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(4);");
         mActivityTestRule.executeJavaScript("uint32_array = new Uint32Array(buffer);");
         mActivityTestRule.executeJavaScript("uint32_array[0] = 4294967295;");
@@ -1124,8 +1092,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassFloat32Array(boolean useMojo) throws Throwable {
+    public void testPassFloat32Array() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(4);");
         mActivityTestRule.executeJavaScript("float32_array = new Float32Array(buffer);");
         mActivityTestRule.executeJavaScript("float32_array[0] = 42.0;");
@@ -1168,8 +1135,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassFloat64Array(boolean useMojo) throws Throwable {
+    public void testPassFloat64Array() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(8);");
         mActivityTestRule.executeJavaScript("float64_array = new Float64Array(buffer);");
         mActivityTestRule.executeJavaScript("float64_array[0] = 42.0;");
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeArrayTest.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeArrayTest.java
index 9794614..24ef476 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeArrayTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeArrayTest.java
@@ -14,11 +14,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.params.BaseJUnit4RunnerDelegate;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content.browser.JavaBridgeActivityTestRule.Controller;
@@ -26,15 +22,12 @@
 /**
  * Part of the test suite for the Java Bridge. This class tests the general use of arrays.
  *
- * The conversions should follow
- * http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS. Places in
- * which the implementation differs from the spec are marked with
- * LIVECONNECT_COMPLIANCE.
- * FIXME: Consider making our implementation more compliant, if it will not
- * break backwards-compatibility. See b/4408210.
+ * <p>The conversions should follow http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS.
+ * Places in which the implementation differs from the spec are marked with LIVECONNECT_COMPLIANCE.
+ * FIXME: Consider making our implementation more compliant, if it will not break
+ * backwards-compatibility. See b/4408210.
  */
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(BaseJUnit4RunnerDelegate.class)
+@RunWith(BaseJUnit4ClassRunner.class)
 @Batch(JavaBridgeActivityTestRule.BATCH)
 public class JavaBridgeArrayTest {
     @Rule public JavaBridgeActivityTestRule mActivityTestRule = new JavaBridgeActivityTestRule();
@@ -115,11 +108,6 @@
         }
     }
 
-    @UseMethodParameterBefore(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void setupMojoTest(boolean useMojo) {
-        mActivityTestRule.setupMojoTest(useMojo);
-    }
-
     private TestObject mTestObject;
 
     @Before
@@ -131,8 +119,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testArrayLength(boolean useMojo) throws Throwable {
+    public void testArrayLength() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setIntArray([42, 43, 44]);");
         int[] result = mTestObject.waitForIntArray();
         Assert.assertEquals(3, result.length);
@@ -144,8 +131,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNull(boolean useMojo) throws Throwable {
+    public void testPassNull() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setIntArray(null);");
         Assert.assertNull(mTestObject.waitForIntArray());
     }
@@ -153,8 +139,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassUndefined(boolean useMojo) throws Throwable {
+    public void testPassUndefined() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setIntArray(undefined);");
         Assert.assertNull(mTestObject.waitForIntArray());
     }
@@ -162,8 +147,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassEmptyArray(boolean useMojo) throws Throwable {
+    public void testPassEmptyArray() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setIntArray([]);");
         Assert.assertEquals(0, mTestObject.waitForIntArray().length);
     }
@@ -173,8 +157,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassArrayToStringMethod(boolean useMojo) throws Throwable {
+    public void testPassArrayToStringMethod() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Should call toString() on array.
         mActivityTestRule.executeJavaScript("testObject.setStringValue([42, 42, 42]);");
         Assert.assertEquals("undefined", mTestObject.waitForStringValue());
@@ -185,8 +168,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassArrayToNonStringNonArrayMethod(boolean useMojo) throws Throwable {
+    public void testPassArrayToNonStringNonArrayMethod() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Should raise JavaScript exception.
         mActivityTestRule.executeJavaScript("testObject.setIntValue([42, 42, 42]);");
         Assert.assertEquals(0, mTestObject.waitForIntValue());
@@ -195,8 +177,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNonArrayToArrayMethod(boolean useMojo) throws Throwable {
+    public void testPassNonArrayToArrayMethod() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Should raise JavaScript exception.
         mActivityTestRule.executeJavaScript("testObject.setIntArray(42);");
         Assert.assertNull(mTestObject.waitForIntArray());
@@ -205,8 +186,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testObjectWithLengthProperty(boolean useMojo) throws Throwable {
+    public void testObjectWithLengthProperty() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setIntArray({length: 3, 1: 42});");
         int[] result = mTestObject.waitForIntArray();
         Assert.assertEquals(3, result.length);
@@ -218,8 +198,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testNonNumericLengthProperty(boolean useMojo) throws Throwable {
+    public void testNonNumericLengthProperty() throws Throwable {
         // LIVECONNECT_COMPLIANCE: This should not count as an array, so we
         // should raise a JavaScript exception.
         mActivityTestRule.executeJavaScript("testObject.setIntArray({length: \"foo\"});");
@@ -229,8 +208,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testLengthOutOfBounds(boolean useMojo) throws Throwable {
+    public void testLengthOutOfBounds() throws Throwable {
         // LIVECONNECT_COMPLIANCE: This should not count as an array, so we
         // should raise a JavaScript exception.
         mActivityTestRule.executeJavaScript("testObject.setIntArray({length: -1});");
@@ -252,8 +230,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testSparseArray(boolean useMojo) throws Throwable {
+    public void testSparseArray() throws Throwable {
         mActivityTestRule.executeJavaScript(
                 "var x = [42, 43]; x[3] = 45; testObject.setIntArray(x);");
         int[] result = mTestObject.waitForIntArray();
@@ -269,8 +246,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testMethodReturningArrayNotCalled(boolean useMojo) throws Throwable {
+    public void testMethodReturningArrayNotCalled() throws Throwable {
         // We don't invoke methods which return arrays, but note that no
         // exception is raised.
         // LIVECONNECT_COMPLIANCE: Should call method and convert result to
@@ -284,8 +260,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testMultiDimensionalArrayMethod(boolean useMojo) throws Throwable {
+    public void testMultiDimensionalArrayMethod() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Should handle multi-dimensional arrays.
         mActivityTestRule.executeJavaScript("testObject.setIntIntArray([ [42, 43], [44, 45] ]);");
         Assert.assertNull(mTestObject.waitForIntIntArray());
@@ -294,8 +269,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassMultiDimensionalArray(boolean useMojo) throws Throwable {
+    public void testPassMultiDimensionalArray() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Should handle multi-dimensional arrays.
         mActivityTestRule.executeJavaScript("testObject.setIntArray([ [42, 43], [44, 45] ]);");
         int[] result = mTestObject.waitForIntArray();
@@ -310,8 +284,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassArrayBuffer(boolean useMojo) throws Throwable {
+    public void testPassArrayBuffer() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(16);");
         mActivityTestRule.executeJavaScript("testObject.setIntArray(buffer);");
         Assert.assertNull(mTestObject.waitForIntArray());
@@ -326,8 +299,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassDataView(boolean useMojo) throws Throwable {
+    public void testPassDataView() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(16);");
         mActivityTestRule.executeJavaScript("testObject.setIntArray(new DataView(buffer));");
         Assert.assertNull(mTestObject.waitForIntArray());
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBareboneTest.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBareboneTest.java
index 5bc3c94..08229e9 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBareboneTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBareboneTest.java
@@ -11,31 +11,19 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.params.BaseJUnit4RunnerDelegate;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper;
 
 /** Common functionality for testing the Java Bridge. */
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(BaseJUnit4RunnerDelegate.class)
+@RunWith(BaseJUnit4ClassRunner.class)
 @Batch(JavaBridgeActivityTestRule.BATCH)
 public class JavaBridgeBareboneTest {
     @Rule public JavaBridgeActivityTestRule mActivityTestRule = new JavaBridgeActivityTestRule();
 
     private TestCallbackHelperContainer mTestCallbackHelperContainer;
-    private boolean mUseMojo;
-
-    @UseMethodParameterBefore(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void setupMojoTest(boolean useMojo) {
-        mUseMojo = useMojo;
-        mActivityTestRule.setupMojoTest(useMojo);
-    }
 
     private void injectDummyObject(final String name) throws Throwable {
         mActivityTestRule.runOnUiThread(
@@ -43,7 +31,7 @@
                     @Override
                     public void run() {
                         mActivityTestRule
-                                .getJavascriptInjector(mUseMojo)
+                                .getJavascriptInjector()
                                 .addPossiblyUnsafeInterface(new Object(), name, null);
                     }
                 });
@@ -70,8 +58,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testImmediateAddition(boolean useMojo) throws Throwable {
+    public void testImmediateAddition() throws Throwable {
         injectDummyObject("testObject");
         Assert.assertEquals("\"object\"", evaluateJsSync("typeof testObject"));
     }
@@ -81,8 +68,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testNoImmediateAdditionAfterJSEvaluation(boolean useMojo) throws Throwable {
+    public void testNoImmediateAdditionAfterJSEvaluation() throws Throwable {
         evaluateJsSync("true");
         injectDummyObject("testObject");
         Assert.assertEquals("\"undefined\"", evaluateJsSync("typeof testObject"));
@@ -91,8 +77,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testImmediateAdditionAfterReload(boolean useMojo) throws Throwable {
+    public void testImmediateAdditionAfterReload() throws Throwable {
         mActivityTestRule.synchronousPageReload();
         injectDummyObject("testObject");
         Assert.assertEquals("\"object\"", evaluateJsSync("typeof testObject"));
@@ -101,8 +86,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testReloadAfterAddition(boolean useMojo) throws Throwable {
+    public void testReloadAfterAddition() throws Throwable {
         injectDummyObject("testObject");
         mActivityTestRule.synchronousPageReload();
         Assert.assertEquals("\"object\"", evaluateJsSync("typeof testObject"));
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java
index cabc8deb7..d68d4d0 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeBasicsTest.java
@@ -17,16 +17,12 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content.browser.JavaBridgeActivityTestRule.Controller;
 import org.chromium.content_public.browser.LoadUrlParams;
-import org.chromium.content_public.browser.test.ContentJUnit4RunnerDelegate;
 import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
 
 import java.lang.annotation.ElementType;
@@ -37,19 +33,12 @@
 import java.util.concurrent.CountDownLatch;
 
 /**
- * Part of the test suite for the Java Bridge. Tests a number of features including ...
- * - The type of injected objects
- * - The type of their methods
- * - Replacing objects
- * - Removing objects
- * - Access control
- * - Calling methods on returned objects
- * - Multiply injected objects
- * - Threading
- * - Inheritance
+ * Part of the test suite for the Java Bridge. Tests a number of features including ... - The type
+ * of injected objects - The type of their methods - Replacing objects - Removing objects - Access
+ * control - Calling methods on returned objects - Multiply injected objects - Threading -
+ * Inheritance
  */
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(ContentJUnit4RunnerDelegate.class)
+@RunWith(BaseJUnit4ClassRunner.class)
 @Batch(JavaBridgeActivityTestRule.BATCH)
 public class JavaBridgeBasicsTest {
     @Rule public JavaBridgeActivityTestRule mActivityTestRule = new JavaBridgeActivityTestRule();
@@ -116,11 +105,6 @@
         }
     }
 
-    @UseMethodParameterBefore(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void setupMojoTest(boolean useMojo) {
-        mActivityTestRule.setupMojoTest(useMojo);
-    }
-
     TestController mTestController;
 
     @Before
@@ -160,16 +144,14 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testTypeOfInjectedObject(boolean useMojo) throws Throwable {
+    public void testTypeOfInjectedObject() throws Throwable {
         Assert.assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
     }
 
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testAdditionNotReflectedUntilReload(boolean useMojo) throws Throwable {
+    public void testAdditionNotReflectedUntilReload() throws Throwable {
         Assert.assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
         InstrumentationRegistry.getInstrumentation()
                 .runOnMainSync(
@@ -177,7 +159,7 @@
                             @Override
                             public void run() {
                                 mActivityTestRule
-                                        .getJavascriptInjector(useMojo)
+                                        .getJavascriptInjector()
                                         .addPossiblyUnsafeInterface(
                                                 new Object(), "testObject", null);
                             }
@@ -190,8 +172,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReplaceWithoutReloading(boolean useMojo) throws Throwable {
+    public void testReplaceWithoutReloading() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -209,7 +190,7 @@
                             @Override
                             public void run() {
                                 mActivityTestRule
-                                        .getJavascriptInjector(useMojo)
+                                        .getJavascriptInjector()
                                         .addPossiblyUnsafeInterface(
                                                 new Object() {
                                                     @JavascriptInterface
@@ -232,8 +213,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testRemovalNotReflectedUntilReload(boolean useMojo) throws Throwable {
+    public void testRemovalNotReflectedUntilReload() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -251,7 +231,7 @@
                             @Override
                             public void run() {
                                 mActivityTestRule
-                                        .getJavascriptInjector(useMojo)
+                                        .getJavascriptInjector()
                                         .removeInterface("testObject");
                             }
                         });
@@ -269,8 +249,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testRemoveObjectNotAdded(boolean useMojo) throws Throwable {
+    public void testRemoveObjectNotAdded() throws Throwable {
         TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
                 mActivityTestRule.getTestCallBackHelperContainer().getOnPageFinishedHelper();
         int currentCallCount = onPageFinishedHelper.getCallCount();
@@ -279,9 +258,7 @@
                         new Runnable() {
                             @Override
                             public void run() {
-                                mActivityTestRule
-                                        .getJavascriptInjector(useMojo)
-                                        .removeInterface("foo");
+                                mActivityTestRule.getJavascriptInjector().removeInterface("foo");
                                 mActivityTestRule
                                         .getWebContents()
                                         .getNavigationController()
@@ -295,8 +272,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testTypeOfMethod(boolean useMojo) throws Throwable {
+    public void testTypeOfMethod() throws Throwable {
         Assert.assertEquals(
                 "function",
                 executeJavaScriptAndGetStringResult("typeof testController.setStringValue"));
@@ -305,8 +281,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testTypeOfInvalidMethod(boolean useMojo) throws Throwable {
+    public void testTypeOfInvalidMethod() throws Throwable {
         Assert.assertEquals(
                 "undefined", executeJavaScriptAndGetStringResult("typeof testController.foo"));
     }
@@ -314,17 +289,14 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCallingInvalidMethodRaisesException(boolean useMojo) throws Throwable {
+    public void testCallingInvalidMethodRaisesException() throws Throwable {
         assertRaisesException("testController.foo()");
     }
 
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testUncaughtJavaExceptionRaisesJavaScriptException(boolean useMojo)
-            throws Throwable {
+    public void testUncaughtJavaExceptionRaisesJavaScriptException() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -339,24 +311,21 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCallingAsConstructorRaisesException(boolean useMojo) throws Throwable {
+    public void testCallingAsConstructorRaisesException() throws Throwable {
         assertRaisesException("new testController.setStringValue('foo')");
     }
 
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCallingOnNonInjectedObjectRaisesException(boolean useMojo) throws Throwable {
+    public void testCallingOnNonInjectedObjectRaisesException() throws Throwable {
         assertRaisesException("testController.setStringValue.call({}, 'foo')");
     }
 
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCallingOnInstanceOfOtherClassRaisesException(boolean useMojo) throws Throwable {
+    public void testCallingOnInstanceOfOtherClassRaisesException() throws Throwable {
         mActivityTestRule.injectObjectAndReload(new Object(), "testObject");
         Assert.assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
         Assert.assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
@@ -370,8 +339,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testTypeOfStaticMethod(boolean useMojo) throws Throwable {
+    public void testTypeOfStaticMethod() throws Throwable {
         mActivityTestRule.injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
         mActivityTestRule.executeJavaScript(
                 "testController.setStringValue(typeof testObject.staticMethod)");
@@ -382,8 +350,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCallStaticMethod(boolean useMojo) throws Throwable {
+    public void testCallStaticMethod() throws Throwable {
         mActivityTestRule.injectObjectAndReload(new ObjectWithStaticMethod(), "testObject");
         mActivityTestRule.executeJavaScript(
                 "testController.setStringValue(testObject.staticMethod())");
@@ -393,8 +360,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testPrivateMethodNotExposed(boolean useMojo) throws Throwable {
+    public void testPrivateMethodNotExposed() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     private void method() {}
@@ -421,8 +387,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReplaceInjectedObject(boolean useMojo) throws Throwable {
+    public void testReplaceInjectedObject() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -449,8 +414,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testInjectNullObjectIsIgnored(boolean useMojo) throws Throwable {
+    public void testInjectNullObjectIsIgnored() throws Throwable {
         mActivityTestRule.injectObjectAndReload(null, "testObject");
         Assert.assertEquals("undefined", executeJavaScriptAndGetStringResult("typeof testObject"));
     }
@@ -458,8 +422,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReplaceInjectedObjectWithNullObjectIsIgnored(boolean useMojo) throws Throwable {
+    public void testReplaceInjectedObjectWithNullObjectIsIgnored() throws Throwable {
         mActivityTestRule.injectObjectAndReload(new Object(), "testObject");
         Assert.assertEquals("object", executeJavaScriptAndGetStringResult("typeof testObject"));
         mActivityTestRule.injectObjectAndReload(null, "testObject");
@@ -469,9 +432,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCallOverloadedMethodWithDifferentNumberOfArguments(boolean useMojo)
-            throws Throwable {
+    public void testCallOverloadedMethodWithDifferentNumberOfArguments() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -505,9 +466,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCallMethodWithWrongNumberOfArgumentsRaisesException(boolean useMojo)
-            throws Throwable {
+    public void testCallMethodWithWrongNumberOfArgumentsRaisesException() throws Throwable {
         assertRaisesException("testController.setIntValue()");
         assertRaisesException("testController.setIntValue(42, 42)");
     }
@@ -515,8 +474,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testObjectPersistsAcrossPageLoads(boolean useMojo) throws Throwable {
+    public void testObjectPersistsAcrossPageLoads() throws Throwable {
         Assert.assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
         mActivityTestRule.synchronousPageReload();
         Assert.assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
@@ -525,8 +483,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCustomPropertiesCleanedUpOnPageReloads(boolean useMojo) throws Throwable {
+    public void testCustomPropertiesCleanedUpOnPageReloads() throws Throwable {
         Assert.assertEquals("object", executeJavaScriptAndGetStringResult("typeof testController"));
         mActivityTestRule.executeJavaScript("testController.myProperty = 42;");
         Assert.assertEquals("42", executeJavaScriptAndGetStringResult("testController.myProperty"));
@@ -539,8 +496,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testSameObjectInjectedMultipleTimes(boolean useMojo) throws Throwable {
+    public void testSameObjectInjectedMultipleTimes() throws Throwable {
         class TestObject {
             private int mNumMethodInvocations;
 
@@ -560,8 +516,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCallMethodOnReturnedObject(boolean useMojo) throws Throwable {
+    public void testCallMethodOnReturnedObject() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -582,8 +537,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReturnedObjectInjectedElsewhere(boolean useMojo) throws Throwable {
+    public void testReturnedObjectInjectedElsewhere() throws Throwable {
         class InnerObject {
             private int mNumMethodInvocations;
 
@@ -616,8 +570,7 @@
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
     @CommandLineFlags.Add("js-flags=--expose-gc")
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReturnedObjectIsGarbageCollected(boolean useMojo) throws Throwable {
+    public void testReturnedObjectIsGarbageCollected() throws Throwable {
         Assert.assertEquals("function", executeJavaScriptAndGetStringResult("typeof gc"));
         class InnerObject {}
         class TestObject {
@@ -665,8 +618,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testSameReturnedObjectUsesSameWrapper(boolean useMojo) throws Throwable {
+    public void testSameReturnedObjectUsesSameWrapper() throws Throwable {
         class InnerObject {}
         final InnerObject innerObject = new InnerObject();
         final Object injectedTestObject =
@@ -688,8 +640,7 @@
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
     @CommandLineFlags.Add("js-flags=--expose-gc")
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testSameWrapperObjectsAreGarbageCollected(boolean useMojo) throws Throwable {
+    public void testSameWrapperObjectsAreGarbageCollected() throws Throwable {
         class InnerObject {}
         class TestObject {
             @JavascriptInterface
@@ -705,7 +656,6 @@
             // A weak reference is used to check InnerObject instance reachability.
             WeakReference<InnerObject> mWeakForInnerObject;
         }
-        ;
         final TestObject injectedTestObject = new TestObject();
 
         mActivityTestRule.injectObjectAndReload(injectedTestObject, "injectedTestObject");
@@ -738,8 +688,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testMethodInvokedOnBackgroundThread(boolean useMojo) throws Throwable {
+    public void testMethodInvokedOnBackgroundThread() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -764,8 +713,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testBlockingUiThreadDoesNotBlockCallsFromJs(boolean useMojo) {
+    public void testBlockingUiThreadDoesNotBlockCallsFromJs() {
         class TestObject {
             private CountDownLatch mLatch;
 
@@ -815,8 +763,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testPublicInheritedMethod(boolean useMojo) throws Throwable {
+    public void testPublicInheritedMethod() throws Throwable {
         class Base {
             @JavascriptInterface
             public void method(int x) {
@@ -834,8 +781,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testPrivateInheritedMethod(boolean useMojo) throws Throwable {
+    public void testPrivateInheritedMethod() throws Throwable {
         class Base {
             @JavascriptInterface
             private void method() {}
@@ -849,8 +795,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testOverriddenMethod(boolean useMojo) throws Throwable {
+    public void testOverriddenMethod() throws Throwable {
         class Base {
             @JavascriptInterface
             public void method() {
@@ -872,8 +817,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testEnumerateMembers(boolean useMojo) throws Throwable {
+    public void testEnumerateMembers() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     public void method() {}
@@ -897,8 +841,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReflectPublicMethod(boolean useMojo) throws Throwable {
+    public void testReflectPublicMethod() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     public Class<?> myGetClass() {
@@ -921,8 +864,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReflectPublicField(boolean useMojo) throws Throwable {
+    public void testReflectPublicField() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     public Class<?> myGetClass() {
@@ -942,8 +884,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReflectPrivateMethodRaisesException(boolean useMojo) throws Throwable {
+    public void testReflectPrivateMethodRaisesException() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     public Class<?> myGetClass() {
@@ -966,8 +907,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReflectPrivateFieldRaisesException(boolean useMojo) throws Throwable {
+    public void testReflectPrivateFieldRaisesException() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -991,8 +931,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testAllowNonAnnotatedMethods(boolean useMojo) throws Throwable {
+    public void testAllowNonAnnotatedMethods() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -1014,8 +953,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testAllowOnlyAnnotatedMethods(boolean useMojo) throws Throwable {
+    public void testAllowOnlyAnnotatedMethods() throws Throwable {
         mActivityTestRule.injectObjectAndReload(
                 new Object() {
                     @JavascriptInterface
@@ -1050,9 +988,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testAnnotationRequirementRetainsPropertyAcrossObjects(boolean useMojo)
-            throws Throwable {
+    public void testAnnotationRequirementRetainsPropertyAcrossObjects() throws Throwable {
         class Test {
             @JavascriptInterface
             public String safe() {
@@ -1104,8 +1040,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testAnnotationDoesNotGetInherited(boolean useMojo) throws Throwable {
+    public void testAnnotationDoesNotGetInherited() throws Throwable {
         class Base {
             @JavascriptInterface
             public void base() {}
@@ -1134,8 +1069,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testCustomAnnotationRestriction(boolean useMojo) throws Throwable {
+    public void testCustomAnnotationRestriction() throws Throwable {
         class Test {
             @TestAnnotation
             public String checkTestAnnotationFoo() {
@@ -1185,8 +1119,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testObjectsInspection(boolean useMojo) throws Throwable {
+    public void testObjectsInspection() throws Throwable {
         class Test {
             @JavascriptInterface
             public String m1() {
@@ -1230,9 +1163,7 @@
                         new Runnable() {
                             @Override
                             public void run() {
-                                mActivityTestRule
-                                        .getJavascriptInjector(useMojo)
-                                        .setAllowInspection(false);
+                                mActivityTestRule.getJavascriptInjector().setAllowInspection(false);
                             }
                         });
 
@@ -1252,8 +1183,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testAccessToObjectGetClassIsBlocked(boolean useMojo) throws Throwable {
+    public void testAccessToObjectGetClassIsBlocked() throws Throwable {
         mActivityTestRule.injectObjectAndReload(new Object(), "testObject", null);
         Assert.assertEquals(
                 "function", executeJavaScriptAndGetStringResult("typeof testObject.getClass"));
@@ -1263,8 +1193,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testReplaceJavascriptInterface(boolean useMojo) throws Throwable {
+    public void testReplaceJavascriptInterface() throws Throwable {
         class Test {
             public Test(int value) {
                 mValue = value;
@@ -1289,8 +1218,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testMethodCalledOnAnotherInstance(boolean useMojo) throws Throwable {
+    public void testMethodCalledOnAnotherInstance() throws Throwable {
         class TestObject {
             private int mIndex;
 
@@ -1320,8 +1248,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testWebViewAfterRenderViewSwapped(boolean useMojo) throws Throwable {
+    public void testWebViewAfterRenderViewSwapped() throws Throwable {
         class TestObject {
             private int mIndex;
 
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeChildFrameTest.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeChildFrameTest.java
index 28d66459..55cea22f 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeChildFrameTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeChildFrameTest.java
@@ -15,10 +15,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
@@ -28,7 +25,6 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.content_public.browser.test.ContentJUnit4RunnerDelegate;
 
 import java.lang.ref.WeakReference;
 import java.util.concurrent.CountDownLatch;
@@ -38,11 +34,9 @@
 /**
  * Part of the test suite for the WebView's Java Bridge.
  *
- * Ensures that injected objects are exposed to child frames as well as the
- * main frame.
+ * <p>Ensures that injected objects are exposed to child frames as well as the main frame.
  */
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(ContentJUnit4RunnerDelegate.class)
+@RunWith(BaseJUnit4ClassRunner.class)
 @Batch(JavaBridgeActivityTestRule.BATCH)
 public class JavaBridgeChildFrameTest {
     @Rule public JavaBridgeActivityTestRule mActivityTestRule = new JavaBridgeActivityTestRule();
@@ -63,11 +57,6 @@
         }
     }
 
-    @UseMethodParameterBefore(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void setupMojoTest(boolean useMojo) {
-        mActivityTestRule.setupMojoTest(useMojo);
-    }
-
     TestController mTestController;
 
     @Before
@@ -76,12 +65,10 @@
         mActivityTestRule.injectObjectAndReload(mTestController, "testController");
     }
 
-    // TODO(crbug.com/40144856): Fix flakiness when using MojoTestParams.
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testInjectedObjectPresentInChildFrame(boolean useMojo) throws Throwable {
+    public void testInjectedObjectPresentInChildFrame() throws Throwable {
         loadDataSync(
                 mActivityTestRule.getWebContents().getNavigationController(),
                 "<html><body><iframe></iframe></body></html>",
@@ -105,8 +92,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testMainPageWrapperIsNotBrokenByChildFrame(boolean useMojo) throws Throwable {
+    public void testMainPageWrapperIsNotBrokenByChildFrame() throws Throwable {
         loadDataSync(
                 mActivityTestRule.getWebContents().getNavigationController(),
                 "<html><body><iframe></iframe></body></html>",
@@ -131,12 +117,10 @@
 
     // Verify that parent page and child frame each has own JS wrapper object.
     // Failing to do so exposes parent's context to the child.
-    // TODO(crbug.com/40144856): Fix flakiness when using MojoTestParams.
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.LegacyTestParams.class)
-    public void testWrapperIsNotSharedWithChildFrame(boolean useMojo) throws Throwable {
+    public void testWrapperIsNotSharedWithChildFrame() throws Throwable {
         // Test by setting a custom property on the parent page's injected
         // object and then checking that child frame doesn't see the property.
         loadDataSync(
@@ -169,8 +153,7 @@
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
     @DisabledTest(message = "https://crbug.com/677053")
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testRemovingTransientObjectHolders(boolean useMojo) throws Throwable {
+    public void testRemovingTransientObjectHolders() throws Throwable {
         class Test {
             private Object mInner = new Object();
             // Expecting the inner object to be retrieved twice.
@@ -241,8 +224,7 @@
     @Feature({"AndroidWebView", "Android-JavaBridge"})
     @CommandLineFlags.Add("js-flags=--expose-gc")
     @DisabledTest(message = "https://crbug.com/646843")
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testHolderFrame(boolean useMojo) throws Throwable {
+    public void testHolderFrame() throws Throwable {
         class Test {
             WeakReference<Object> mWeakRefForInner;
             private CountDownLatch mLatch = new CountDownLatch(1);
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeCoercionTest.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeCoercionTest.java
index 6d57eb49..1ed48ba 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeCoercionTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeCoercionTest.java
@@ -14,11 +14,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.params.BaseJUnit4RunnerDelegate;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.UrlUtils;
@@ -27,19 +23,15 @@
 import java.io.File;
 
 /**
- * Part of the test suite for the Java Bridge. This class tests that
- * we correctly convert JavaScript values to Java values when passing them to
- * the methods of injected Java objects.
+ * Part of the test suite for the Java Bridge. This class tests that we correctly convert JavaScript
+ * values to Java values when passing them to the methods of injected Java objects.
  *
- * The conversions should follow
- * http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS. Places in
- * which the implementation differs from the spec are marked with
- * LIVECONNECT_COMPLIANCE.
- * FIXME: Consider making our implementation more compliant, if it will not
- * break backwards-compatibility. See b/4408210.
+ * <p>The conversions should follow http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS.
+ * Places in which the implementation differs from the spec are marked with LIVECONNECT_COMPLIANCE.
+ * FIXME: Consider making our implementation more compliant, if it will not break
+ * backwards-compatibility. See b/4408210.
  */
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(BaseJUnit4RunnerDelegate.class)
+@RunWith(BaseJUnit4ClassRunner.class)
 @Batch(JavaBridgeActivityTestRule.BATCH)
 public class JavaBridgeCoercionTest {
     private static final double ASSERTION_DELTA = 0;
@@ -197,11 +189,6 @@
 
     private static class CustomType2 {}
 
-    @UseMethodParameterBefore(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void setupMojoTest(boolean useMojo) {
-        mActivityTestRule.setupMojoTest(useMojo);
-    }
-
     private TestObject mTestObject;
 
     private static class TestController extends Controller {
@@ -248,8 +235,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNumberInt32(boolean useMojo) throws Throwable {
+    public void testPassNumberInt32() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setByteValue(42);");
         Assert.assertEquals(42, mTestObject.waitForByteValue());
         mActivityTestRule.executeJavaScript(
@@ -302,8 +288,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNumberDouble(boolean useMojo) throws Throwable {
+    public void testPassNumberDouble() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setByteValue(42.1);");
         Assert.assertEquals(42, mTestObject.waitForByteValue());
         mActivityTestRule.executeJavaScript(
@@ -392,8 +377,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNumberNaN(boolean useMojo) throws Throwable {
+    public void testPassNumberNaN() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setByteValue(Number.NaN);");
         Assert.assertEquals(0, mTestObject.waitForByteValue());
 
@@ -434,8 +418,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNumberInfinity(boolean useMojo) throws Throwable {
+    public void testPassNumberInfinity() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setByteValue(Infinity);");
         Assert.assertEquals(-1, mTestObject.waitForByteValue());
 
@@ -479,8 +462,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassBoolean(boolean useMojo) throws Throwable {
+    public void testPassBoolean() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setBooleanValue(true);");
         Assert.assertTrue(mTestObject.waitForBooleanValue());
         mActivityTestRule.executeJavaScript("testObject.setBooleanValue(false);");
@@ -546,8 +528,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassString(boolean useMojo) throws Throwable {
+    public void testPassString() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setStringValue(\"+042.10\");");
         Assert.assertEquals("+042.10", mTestObject.waitForStringValue());
 
@@ -600,8 +581,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassJavaScriptObject(boolean useMojo) throws Throwable {
+    public void testPassJavaScriptObject() throws Throwable {
         // LIVECONNECT_COMPLIANCE: Should raise a JavaScript exception.
         mActivityTestRule.executeJavaScript("testObject.setObjectValue({foo: 42});");
         Assert.assertNull(mTestObject.waitForObjectValue());
@@ -653,8 +633,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassJavaObject(boolean useMojo) throws Throwable {
+    public void testPassJavaObject() throws Throwable {
         mActivityTestRule.executeJavaScript(
                 "testObject.setObjectValue(testObject.getObjectInstance());");
         Assert.assertTrue(mTestObject.getObjectInstance() == mTestObject.waitForObjectValue());
@@ -733,8 +712,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassJavaObjectFromCustomClassLoader(boolean useMojo) throws Throwable {
+    public void testPassJavaObjectFromCustomClassLoader() throws Throwable {
         // Compiled bytecode (dex) for the following class:
         //
         // package org.example;
@@ -764,7 +742,7 @@
                     @Override
                     public void run() {
                         mActivityTestRule
-                                .getJavascriptInjector(useMojo)
+                                .getJavascriptInjector()
                                 .addPossiblyUnsafeInterface(selfConsuming, "selfConsuming", null);
                     }
                 });
@@ -779,8 +757,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassNull(boolean useMojo) throws Throwable {
+    public void testPassNull() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setObjectValue(null);");
         Assert.assertNull(mTestObject.waitForObjectValue());
 
@@ -819,8 +796,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassUndefined(boolean useMojo) throws Throwable {
+    public void testPassUndefined() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setObjectValue(undefined);");
         Assert.assertNull(mTestObject.waitForObjectValue());
 
@@ -861,8 +837,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassArrayBuffer(boolean useMojo) throws Throwable {
+    public void testPassArrayBuffer() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(16);");
 
         mActivityTestRule.executeJavaScript("testObject.setObjectValue(buffer);");
@@ -879,8 +854,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassDataView(boolean useMojo) throws Throwable {
+    public void testPassDataView() throws Throwable {
         mActivityTestRule.executeJavaScript("buffer = new ArrayBuffer(16);");
 
         mActivityTestRule.executeJavaScript("testObject.setObjectValue(new DataView(buffer));");
@@ -894,8 +868,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassDateObject(boolean useMojo) throws Throwable {
+    public void testPassDateObject() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setDoubleValue(new Date(2000, 0, 1));");
         Assert.assertEquals(0.0, mTestObject.waitForDoubleValue(), ASSERTION_DELTA);
 
@@ -910,8 +883,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassRegExpObject(boolean useMojo) throws Throwable {
+    public void testPassRegExpObject() throws Throwable {
         mActivityTestRule.executeJavaScript("testObject.setStringValue(/abc/);");
         Assert.assertEquals("undefined", mTestObject.waitForStringValue());
 
@@ -923,8 +895,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testPassFunctionObject(boolean useMojo) throws Throwable {
+    public void testPassFunctionObject() throws Throwable {
         mActivityTestRule.executeJavaScript("func = new Function('a', 'b', 'return a + b');");
 
         mActivityTestRule.executeJavaScript("testObject.setStringValue(func);");
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeFieldsTest.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeFieldsTest.java
index fe106b1..c420a2d 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeFieldsTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeFieldsTest.java
@@ -14,21 +14,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.params.BaseJUnit4RunnerDelegate;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content.browser.JavaBridgeActivityTestRule.Controller;
 
-/**
- * Part of the test suite for the Java Bridge. This test tests the
- * use of fields.
- */
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(BaseJUnit4RunnerDelegate.class)
+/** Part of the test suite for the Java Bridge. This test tests the use of fields. */
+@RunWith(BaseJUnit4ClassRunner.class)
 @Batch(JavaBridgeActivityTestRule.BATCH)
 public class JavaBridgeFieldsTest {
     @Rule public JavaBridgeActivityTestRule mActivityTestRule = new JavaBridgeActivityTestRule();
@@ -65,11 +57,6 @@
     // A custom type used when testing passing objects.
     private static class CustomType {}
 
-    @UseMethodParameterBefore(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void setupMojoTest(boolean useMojo) {
-        mActivityTestRule.setupMojoTest(useMojo);
-    }
-
     TestObject mTestObject;
 
     @Before
@@ -89,8 +76,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testFieldTypes(boolean useMojo) throws Throwable {
+    public void testFieldTypes() throws Throwable {
         Assert.assertEquals(
                 "undefined", executeJavaScriptAndGetStringResult("typeof testObject.booleanField"));
         Assert.assertEquals(
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeReturnValuesTest.java b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeReturnValuesTest.java
index 386b9d1..be7d6ac 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeReturnValuesTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeReturnValuesTest.java
@@ -14,11 +14,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import org.chromium.base.test.params.BaseJUnit4RunnerDelegate;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter;
-import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameterBefore;
-import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
-import org.chromium.base.test.params.ParameterizedRunner;
+import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content.browser.JavaBridgeActivityTestRule.Controller;
@@ -27,15 +23,12 @@
  * Part of the test suite for the Java Bridge. This test checks that we correctly convert Java
  * values to JavaScript values when returning them from the methods of injected Java objects.
  *
- * The conversions should follow
- * http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS. Places in
- * which the implementation differs from the spec are marked with
- * LIVECONNECT_COMPLIANCE.
- * FIXME: Consider making our implementation more compliant, if it will not
- * break backwards-compatibility. See b/4408210.
+ * <p>The conversions should follow http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS.
+ * Places in which the implementation differs from the spec are marked with LIVECONNECT_COMPLIANCE.
+ * FIXME: Consider making our implementation more compliant, if it will not break
+ * backwards-compatibility. See b/4408210.
  */
-@RunWith(ParameterizedRunner.class)
-@UseRunnerDelegate(BaseJUnit4RunnerDelegate.class)
+@RunWith(BaseJUnit4ClassRunner.class)
 @Batch(JavaBridgeActivityTestRule.BATCH)
 public class JavaBridgeReturnValuesTest {
     @Rule public JavaBridgeActivityTestRule mActivityTestRule = new JavaBridgeActivityTestRule();
@@ -156,11 +149,6 @@
     // A custom type used when testing passing objects.
     private static class CustomType {}
 
-    @UseMethodParameterBefore(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void setupMojoTest(boolean useMojo) {
-        mActivityTestRule.setupMojoTest(useMojo);
-    }
-
     TestObject mTestObject;
 
     @Before
@@ -184,8 +172,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testMethodReturnTypes(boolean useMojo) throws Throwable {
+    public void testMethodReturnTypes() throws Throwable {
         Assert.assertEquals(
                 "boolean",
                 executeJavaScriptAndGetStringResult("typeof testObject.getBooleanValue()"));
@@ -238,8 +225,7 @@
     @Test
     @SmallTest
     @Feature({"AndroidWebView", "Android-JavaBridge"})
-    @UseMethodParameter(JavaBridgeActivityTestRule.MojoTestParams.class)
-    public void testMethodReturnValues(boolean useMojo) throws Throwable {
+    public void testMethodReturnValues() throws Throwable {
         // We do the string comparison in JavaScript, to avoid relying on the
         // coercion algorithm from JavaScript to Java.
         Assert.assertTrue(executeJavaScriptAndGetBooleanResult("testObject.getBooleanValue()"));
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/remoteobjects/DIR_METADATA b/content/public/android/javatests/src/org/chromium/content/browser/remoteobjects/DIR_METADATA
deleted file mode 100644
index a1576e2b..0000000
--- a/content/public/android/javatests/src/org/chromium/content/browser/remoteobjects/DIR_METADATA
+++ /dev/null
@@ -1 +0,0 @@
-mixins: "//content/public/android/java/src/org/chromium/content/browser/remoteobjects/COMMON_METADATA"
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/remoteobjects/OWNERS b/content/public/android/javatests/src/org/chromium/content/browser/remoteobjects/OWNERS
deleted file mode 100644
index 52fd0ad..0000000
--- a/content/public/android/javatests/src/org/chromium/content/browser/remoteobjects/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file://content/public/android/java/src/org/chromium/content/browser/remoteobjects/OWNERS
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/remoteobjects/RemoteObjectHostImplTest.java b/content/public/android/javatests/src/org/chromium/content/browser/remoteobjects/RemoteObjectHostImplTest.java
deleted file mode 100644
index 964a73dd..0000000
--- a/content/public/android/javatests/src/org/chromium/content/browser/remoteobjects/RemoteObjectHostImplTest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser.remoteobjects;
-
-import static org.hamcrest.Matchers.isIn;
-import static org.hamcrest.Matchers.not;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.base.test.util.Feature;
-import org.chromium.blink.mojom.RemoteObject;
-import org.chromium.mojo.MojoTestRule;
-import org.chromium.mojo.bindings.ConnectionErrorHandler;
-import org.chromium.mojo.bindings.InterfaceRequest;
-import org.chromium.mojo.system.MojoException;
-import org.chromium.mojo.system.Pair;
-import org.chromium.mojo.system.impl.CoreImpl;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Tests the Mojo interface which vends RemoteObject interface handles.
- * Ensures that the provided handles are properly bound.
- */
-@RunWith(BaseJUnit4ClassRunner.class)
-public final class RemoteObjectHostImplTest {
-    @Rule public MojoTestRule mMojoTestRule = new MojoTestRule(MojoTestRule.MojoCore.INITIALIZE);
-
-    private final Set<RemoteObjectRegistry> mRetainingSet = new HashSet<>();
-    private final RemoteObjectRegistry mRegistry = new RemoteObjectRegistry(mRetainingSet);
-
-    /**
-     * Annotation which can be used in the way that {@link android.webkit.JavascriptInterface}
-     * would.
-     */
-    @Retention(RetentionPolicy.RUNTIME)
-    @Target({ElementType.METHOD})
-    private @interface TestJavascriptInterface {}
-
-    /** {@link ConnectionErrorHandler} that records any error it received. */
-    private static class CapturingErrorHandler implements ConnectionErrorHandler {
-        private MojoException mLastMojoException;
-
-        /**
-         * @see ConnectionErrorHandler#onConnectionError(MojoException)
-         */
-        @Override
-        public void onConnectionError(MojoException e) {
-            mLastMojoException = e;
-        }
-
-        /** Returns the last recorded exception. */
-        public MojoException getLastMojoException() {
-            return mLastMojoException;
-        }
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"AndroidWebView", "Android-JavaBridge"})
-    public void testClosesPipeIfObjectDoesNotExist() {
-        RemoteObjectHostImpl host =
-                new RemoteObjectHostImpl(
-                        /* auditor= */ null, mRegistry, /* allowInspection= */ true);
-
-        Pair<RemoteObject.Proxy, InterfaceRequest<RemoteObject>> result =
-                RemoteObject.MANAGER.getInterfaceRequest(CoreImpl.getInstance());
-        CapturingErrorHandler errorHandler = new CapturingErrorHandler();
-        result.first.getProxyHandler().setErrorHandler(errorHandler);
-        host.getObject(123, result.second);
-
-        mMojoTestRule.runLoopUntilIdle();
-        Assert.assertNotNull(errorHandler.getLastMojoException());
-    }
-
-    /**
-     * Tiny utility to capture the result of using RemoteObject.
-     *
-     * This verifies that it is working correctly.
-     */
-    private static class HasMethodCapture implements RemoteObject.HasMethod_Response {
-        public Boolean methodExists;
-
-        @Override
-        public void call(boolean result) {
-            methodExists = result;
-        }
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"AndroidWebView", "Android-JavaBridge"})
-    public void testBindsPipeIfObjectExists() {
-        Object o =
-                new Object() {
-                    @TestJavascriptInterface
-                    public void frobnicate() {}
-                };
-        int id = mRegistry.getObjectId(o, TestJavascriptInterface.class);
-
-        RemoteObjectHostImpl host =
-                new RemoteObjectHostImpl(
-                        /* auditor= */ null, mRegistry, /* allowInspection= */ true);
-
-        Pair<RemoteObject.Proxy, InterfaceRequest<RemoteObject>> result =
-                RemoteObject.MANAGER.getInterfaceRequest(CoreImpl.getInstance());
-        RemoteObject.Proxy remoteObject = result.first;
-        CapturingErrorHandler errorHandler = new CapturingErrorHandler();
-        remoteObject.getProxyHandler().setErrorHandler(errorHandler);
-        host.getObject(id, result.second);
-
-        HasMethodCapture frobnicate = new HasMethodCapture();
-        remoteObject.hasMethod("frobnicate", frobnicate);
-        HasMethodCapture nonExistentMethod = new HasMethodCapture();
-        remoteObject.hasMethod("nonExistentMethod", nonExistentMethod);
-
-        mMojoTestRule.runLoopUntilIdle();
-        Assert.assertNull(errorHandler.getLastMojoException());
-        Assert.assertEquals(true, frobnicate.methodExists);
-        Assert.assertEquals(false, nonExistentMethod.methodExists);
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"AndroidWebView", "Android-JavaBridge"})
-    public void testRelease() {
-        Object o = new Object();
-        int id = mRegistry.getObjectId(o, TestJavascriptInterface.class);
-
-        RemoteObjectHostImpl host =
-                new RemoteObjectHostImpl(
-                        /* auditor= */ null, mRegistry, /* allowInspection= */ true);
-
-        Assert.assertSame(o, mRegistry.getObjectById(id));
-        host.releaseObject(id);
-        Assert.assertNull(mRegistry.getObjectById(id));
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"AndroidWebView", "Android-JavaBridge"})
-    public void testClose() {
-        RemoteObjectHostImpl host =
-                new RemoteObjectHostImpl(
-                        /* auditor= */ null, mRegistry, /* allowInspection= */ true);
-        Assert.assertThat(mRegistry, isIn(mRetainingSet));
-        host.close();
-        Assert.assertThat(mRegistry, not(isIn(mRetainingSet)));
-    }
-
-    @Test
-    @SmallTest
-    @Feature({"AndroidWebView", "Android-JavaBridge"})
-    public void testClosePipeAfterHostClosesWithoutRelease() {
-        Object o = new Object();
-        int id = mRegistry.getObjectId(o, TestJavascriptInterface.class);
-
-        RemoteObjectHostImpl host =
-                new RemoteObjectHostImpl(
-                        /* auditor= */ null, mRegistry, /* allowInspection= */ true);
-
-        Pair<RemoteObject.Proxy, InterfaceRequest<RemoteObject>> result =
-                RemoteObject.MANAGER.getInterfaceRequest(CoreImpl.getInstance());
-        RemoteObject.Proxy remoteObject = result.first;
-        host.getObject(id, result.second);
-        host.close();
-
-        Assert.assertSame(o, mRegistry.getObjectById(id));
-        remoteObject.close();
-
-        mMojoTestRule.runLoopUntilIdle();
-        Assert.assertNull(mRegistry.getObjectById(id));
-    }
-}
diff --git a/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/DIR_METADATA b/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/DIR_METADATA
deleted file mode 100644
index a1576e2b..0000000
--- a/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/DIR_METADATA
+++ /dev/null
@@ -1 +0,0 @@
-mixins: "//content/public/android/java/src/org/chromium/content/browser/remoteobjects/COMMON_METADATA"
diff --git a/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/OWNERS b/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/OWNERS
deleted file mode 100644
index 52fd0ad..0000000
--- a/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file://content/public/android/java/src/org/chromium/content/browser/remoteobjects/OWNERS
diff --git a/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/RemoteObjectImplTest.java b/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/RemoteObjectImplTest.java
deleted file mode 100644
index a8dbd71..0000000
--- a/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/RemoteObjectImplTest.java
+++ /dev/null
@@ -1,1216 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser.remoteobjects;
-
-import static org.mockito.AdditionalMatchers.and;
-import static org.mockito.AdditionalMatchers.aryEq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import org.chromium.blink.mojom.RemoteArrayType;
-import org.chromium.blink.mojom.RemoteInvocationArgument;
-import org.chromium.blink.mojom.RemoteInvocationError;
-import org.chromium.blink.mojom.RemoteInvocationResult;
-import org.chromium.blink.mojom.RemoteInvocationResultValue;
-import org.chromium.blink.mojom.RemoteObject;
-import org.chromium.blink.mojom.RemoteTypedArray;
-import org.chromium.blink.mojom.SingletonJavaScriptValue;
-import org.chromium.mojo_base.BigBufferUtil;
-import org.chromium.mojo_base.mojom.String16;
-
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.util.Arrays;
-import java.util.function.Consumer;
-
-/**
- * Tests the implementation of the Mojo object which wraps invocations
- * of Java methods.
- *
- * Unchecked cast warnings are suppressed because {@link org.mockito.Mockito#mock(Class)} does not
- * provide a way to cleanly deal with generics.
- */
-@SuppressWarnings("unchecked")
-@RunWith(BlockJUnit4ClassRunner.class)
-public final class RemoteObjectImplTest {
-    /**
-     * Annotation which can be used in the way that {@link android.webkit.JavascriptInterface}
-     * would.
-     *
-     * A separate one is used to ensure that RemoteObject is actually respecting the parameter.
-     */
-    @Retention(RetentionPolicy.RUNTIME)
-    @Target({ElementType.METHOD})
-    private @interface TestJavascriptInterface {}
-
-    @Mock private RemoteObjectImpl.Auditor mAuditor;
-
-    @Mock private RemoteObjectImpl.ObjectIdAllocator mIdAllocator;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void testHasMethodWithSafeAnnotationClass() {
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public void exposedMethod() {}
-
-                    @TestJavascriptInterface
-                    public void anotherExposedMethod() {}
-
-                    @TestJavascriptInterface
-                    public void anotherExposedMethod(int x) {}
-
-                    @TestJavascriptInterface
-                    private void privateAnnotatedMethod() {}
-
-                    public void unannotatedMethod() {}
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.HasMethod_Response hasMethodResponse;
-
-        // This method is public and annotated; it should be exposed.
-        hasMethodResponse = mock(RemoteObject.HasMethod_Response.class);
-        remoteObject.hasMethod("exposedMethod", hasMethodResponse);
-        verify(hasMethodResponse).call(true);
-
-        // This method is private; it should not be exposed.
-        hasMethodResponse = mock(RemoteObject.HasMethod_Response.class);
-        remoteObject.hasMethod("privateAnnotatedMethod", hasMethodResponse);
-        verify(hasMethodResponse).call(false);
-
-        // This method is not annotated; it should not be exposed.
-        hasMethodResponse = mock(RemoteObject.HasMethod_Response.class);
-        remoteObject.hasMethod("unannotatedMethod", hasMethodResponse);
-        verify(hasMethodResponse).call(false);
-
-        // getMethods should provide a result consistent with this.
-        // The result must also be in sorted order and have no duplicates.
-        RemoteObject.GetMethods_Response getMethodsResponse =
-                mock(RemoteObject.GetMethods_Response.class);
-        remoteObject.getMethods(getMethodsResponse);
-        verify(getMethodsResponse)
-                .call(aryEq(new String[] {"anotherExposedMethod", "exposedMethod"}));
-    }
-
-    @Test
-    public void testHasMethodWithoutSafeAnnotationClass() {
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public void annotatedMethod() {}
-
-                    public void unannotatedMethod() {}
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, null);
-        RemoteObject.HasMethod_Response hasMethodResponse;
-
-        // This method has an annotation; it should be exposed.
-        hasMethodResponse = mock(RemoteObject.HasMethod_Response.class);
-        remoteObject.hasMethod("annotatedMethod", hasMethodResponse);
-        verify(hasMethodResponse).call(true);
-
-        // This method doesn't, but passing null skips the check.
-        hasMethodResponse = mock(RemoteObject.HasMethod_Response.class);
-        remoteObject.hasMethod("unannotatedMethod", hasMethodResponse);
-        verify(hasMethodResponse).call(true);
-
-        // getMethods should provide a result consistent with this.
-        // The result must also be in sorted order.
-        // Note that this includes all of the normal java.lang.Object methods.
-        RemoteObject.GetMethods_Response getMethodsResponse =
-                mock(RemoteObject.GetMethods_Response.class);
-        remoteObject.getMethods(getMethodsResponse);
-
-        ArgumentCaptor<String[]> methodsCaptor = ArgumentCaptor.forClass(String[].class);
-        verify(getMethodsResponse).call(methodsCaptor.capture());
-        String[] methods = methodsCaptor.getValue();
-        Assert.assertTrue(Arrays.asList(methods).contains("annotatedMethod"));
-        Assert.assertTrue(Arrays.asList(methods).contains("unannotatedMethod"));
-        Assert.assertTrue(Arrays.asList(methods).contains("hashCode"));
-        String[] sortedMethods = Arrays.copyOf(methods, methods.length);
-        Arrays.sort(sortedMethods);
-        Assert.assertArrayEquals(sortedMethods, methods);
-    }
-
-    @Test
-    public void testInvokeMethodBasic() {
-        final Runnable runnable = mock(Runnable.class);
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public void frobnicate() {
-                        runnable.run();
-                    }
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("frobnicate", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod("frobnicate", new RemoteInvocationArgument[] {}, response);
-
-        verify(runnable, times(2)).run();
-        verify(response, times(2)).call(resultIsOk());
-    }
-
-    @Test
-    public void testInvokeMethodOverloadUsingArity() {
-        final Consumer<Integer> consumer = (Consumer<Integer>) mock(Consumer.class);
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public void frobnicate() {
-                        consumer.accept(0);
-                    }
-
-                    @TestJavascriptInterface
-                    public void frobnicate(Object argument) {
-                        consumer.accept(1);
-                    }
-                };
-
-        // The method overload to be called depends on the number of arguments supplied.
-        // TODO(jbroman): Once it's possible to construct a non-trivial argument, do so.
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("frobnicate", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod(
-                "frobnicate", new RemoteInvocationArgument[] {numberArgument(0)}, response);
-
-        InOrder inOrder = inOrder(consumer);
-        inOrder.verify(consumer).accept(0);
-        inOrder.verify(consumer).accept(1);
-        verify(response, times(2)).call(resultIsOk());
-    }
-
-    /**
-     * Reports to the runnable it is given when its static method is called.
-     * Works around the fact that a static method cannot capture variables.
-     */
-    static class ObjectWithStaticMethod {
-        static Runnable sRunnable;
-
-        @TestJavascriptInterface
-        public static void staticMethod() {
-            sRunnable.run();
-        }
-    }
-
-    @Test
-    public void testStaticMethod() {
-        // Static methods should work just like non-static ones.
-
-        Object target = new ObjectWithStaticMethod();
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-
-        Runnable runnable = mock(Runnable.class);
-        ObjectWithStaticMethod.sRunnable = runnable;
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("staticMethod", new RemoteInvocationArgument[] {}, response);
-        ObjectWithStaticMethod.sRunnable = null;
-        verify(runnable).run();
-        verify(response).call(resultIsOk());
-
-        RemoteObject.HasMethod_Response hasMethodResponse =
-                mock(RemoteObject.HasMethod_Response.class);
-        remoteObject.hasMethod("staticMethod", hasMethodResponse);
-        verify(hasMethodResponse).call(true);
-
-        RemoteObject.GetMethods_Response getMethodsResponse =
-                mock(RemoteObject.GetMethods_Response.class);
-        remoteObject.getMethods(getMethodsResponse);
-        verify(getMethodsResponse).call(aryEq(new String[] {"staticMethod"}));
-    }
-
-    @Test
-    public void testInvokeMethodNotFound() {
-        Object target =
-                new Object() {
-                    public void unexposedMethod() {
-                        Assert.fail("Unexposed method should not be called.");
-                    }
-
-                    @TestJavascriptInterface
-                    public void exposedMethodWithWrongArity(Object argument) {
-                        Assert.fail("Exposed method should only be called with the correct arity.");
-                    }
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("nonexistentMethod", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod("unexposedMethod", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod(
-                "exposedMethodWithWrongArity", new RemoteInvocationArgument[] {}, response);
-
-        verify(response, times(3)).call(resultHasError(RemoteInvocationError.METHOD_NOT_FOUND));
-    }
-
-    @Test
-    public void testGetMethodsWithDisallowedInspection() {
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public void exposedMethod() {}
-
-                    @TestJavascriptInterface
-                    public void anotherExposedMethod() {}
-                };
-
-        RemoteObject remoteObject =
-                newRemoteObjectImpl(
-                        target, TestJavascriptInterface.class, /* allowInspection= */ false);
-
-        // getMethods should be empty.
-        RemoteObject.GetMethods_Response getMethodsResponse =
-                mock(RemoteObject.GetMethods_Response.class);
-        remoteObject.getMethods(getMethodsResponse);
-        verify(getMethodsResponse).call(aryEq(new String[] {}));
-    }
-
-    @Test
-    public void testObjectGetClassBlocked() {
-        Object target = new Object();
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        RemoteObject remoteObject = newRemoteObjectImpl(target, null);
-        remoteObject.invokeMethod("getClass", new RemoteInvocationArgument[] {}, response);
-
-        verify(response).call(resultHasError(RemoteInvocationError.OBJECT_GET_CLASS_BLOCKED));
-        verify(mAuditor).onObjectGetClassInvocationAttempt();
-    }
-
-    @Test
-    public void testOverloadedGetClassPermitted() {
-        final Runnable runnable = mock(Runnable.class);
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public void getClass(Object o) {
-                        runnable.run();
-                    }
-                };
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        remoteObject.invokeMethod(
-                "getClass", new RemoteInvocationArgument[] {numberArgument(0)}, response);
-
-        verify(runnable).run();
-        verify(response).call(resultIsOk());
-        verify(mAuditor, never()).onObjectGetClassInvocationAttempt();
-    }
-
-    @Test
-    public void testMethodReturningArrayIgnored() {
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public int[] returnsIntArray() {
-                        Assert.fail("Method returning array should not be called.");
-                        return null;
-                    }
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("returnsIntArray", new RemoteInvocationArgument[] {}, response);
-
-        verify(response).call(resultIsUndefined());
-    }
-
-    @Test
-    public void testInvocationTargetException() {
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public void exceptionThrowingMethod() throws Exception {
-                        throw new Exception(
-                                "This exception is expected during test. Do not be alarmed.");
-                    }
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod(
-                "exceptionThrowingMethod", new RemoteInvocationArgument[] {}, response);
-
-        verify(response).call(resultHasError(RemoteInvocationError.EXCEPTION_THROWN));
-    }
-
-    private static class VariantConsumer {
-        private final Consumer<Object> mConsumer;
-
-        public VariantConsumer(Consumer<Object> consumer) {
-            mConsumer = consumer;
-        }
-
-        @TestJavascriptInterface
-        public void consumeByte(byte b) {
-            mConsumer.accept(b);
-        }
-
-        @TestJavascriptInterface
-        public void consumeChar(char c) {
-            mConsumer.accept(c);
-        }
-
-        @TestJavascriptInterface
-        public void consumeShort(short s) {
-            mConsumer.accept(s);
-        }
-
-        @TestJavascriptInterface
-        public void consumeInt(int i) {
-            mConsumer.accept(i);
-        }
-
-        @TestJavascriptInterface
-        public void consumeLong(long l) {
-            mConsumer.accept(l);
-        }
-
-        @TestJavascriptInterface
-        public void consumeFloat(float f) {
-            mConsumer.accept(f);
-        }
-
-        @TestJavascriptInterface
-        public void consumeDouble(double d) {
-            mConsumer.accept(d);
-        }
-
-        @TestJavascriptInterface
-        public void consumeBoolean(boolean b) {
-            mConsumer.accept(b);
-        }
-
-        @TestJavascriptInterface
-        public void consumeString(String s) {
-            mConsumer.accept(s);
-        }
-
-        @TestJavascriptInterface
-        public void consumeObjectArray(Object[] oa) {
-            mConsumer.accept(oa);
-        }
-
-        @TestJavascriptInterface
-        public void consumeBooleanArray(boolean[] ba) {
-            mConsumer.accept(ba);
-        }
-
-        @TestJavascriptInterface
-        public void consumeIntArray(int[] ia) {
-            mConsumer.accept(ia);
-        }
-
-        @TestJavascriptInterface
-        public void consumeFloatArray(float[] fa) {
-            mConsumer.accept(fa);
-        }
-
-        @TestJavascriptInterface
-        public void consumeDoubleArray(double[] da) {
-            mConsumer.accept(da);
-        }
-
-        @TestJavascriptInterface
-        public void consumeStringArray(String[] sa) {
-            mConsumer.accept(sa);
-        }
-
-        @TestJavascriptInterface
-        public void consumeObject(Object o) {
-            mConsumer.accept(o);
-        }
-    }
-
-    @Test
-    public void testArgumentConversionNumber() {
-        final Consumer<Object> consumer = (Consumer<Object>) mock(Consumer.class);
-        Object target = new VariantConsumer(consumer);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod(
-                "consumeByte", new RemoteInvocationArgument[] {numberArgument(356)}, response);
-        remoteObject.invokeMethod(
-                "consumeChar", new RemoteInvocationArgument[] {numberArgument(356)}, response);
-        remoteObject.invokeMethod(
-                "consumeChar", new RemoteInvocationArgument[] {numberArgument(1.5)}, response);
-        remoteObject.invokeMethod(
-                "consumeChar", new RemoteInvocationArgument[] {numberArgument(-0.0)}, response);
-        remoteObject.invokeMethod(
-                "consumeShort", new RemoteInvocationArgument[] {numberArgument(32768)}, response);
-        remoteObject.invokeMethod(
-                "consumeInt", new RemoteInvocationArgument[] {numberArgument(-1.5)}, response);
-        remoteObject.invokeMethod(
-                "consumeLong",
-                new RemoteInvocationArgument[] {numberArgument(Double.POSITIVE_INFINITY)},
-                response);
-        remoteObject.invokeMethod(
-                "consumeFloat",
-                new RemoteInvocationArgument[] {numberArgument(3.141592654)},
-                response);
-        remoteObject.invokeMethod(
-                "consumeDouble",
-                new RemoteInvocationArgument[] {numberArgument(Double.NaN)},
-                response);
-        remoteObject.invokeMethod(
-                "consumeBoolean", new RemoteInvocationArgument[] {numberArgument(1)}, response);
-        remoteObject.invokeMethod(
-                "consumeString",
-                new RemoteInvocationArgument[] {numberArgument(-1.66666666666)},
-                response);
-        remoteObject.invokeMethod(
-                "consumeString",
-                new RemoteInvocationArgument[] {numberArgument(Double.NaN)},
-                response);
-        remoteObject.invokeMethod(
-                "consumeString",
-                new RemoteInvocationArgument[] {numberArgument(Double.NEGATIVE_INFINITY)},
-                response);
-        remoteObject.invokeMethod(
-                "consumeString", new RemoteInvocationArgument[] {numberArgument(-0.0)}, response);
-        remoteObject.invokeMethod(
-                "consumeString",
-                new RemoteInvocationArgument[] {numberArgument(123456789)},
-                response);
-        remoteObject.invokeMethod(
-                "consumeString",
-                new RemoteInvocationArgument[] {numberArgument(123000000.1)},
-                response);
-        remoteObject.invokeMethod(
-                "consumeObjectArray", new RemoteInvocationArgument[] {numberArgument(6)}, response);
-        remoteObject.invokeMethod(
-                "consumeObject", new RemoteInvocationArgument[] {numberArgument(6)}, response);
-
-        verify(consumer).accept((byte) 100);
-        verify(consumer).accept('\u0164');
-        verify(consumer, times(2)).accept('\u0000');
-        verify(consumer).accept((short) -32768);
-        verify(consumer).accept((int) -1);
-        verify(consumer).accept(Long.MAX_VALUE);
-        verify(consumer).accept((float) 3.141592654);
-        verify(consumer).accept(Double.NaN);
-        verify(consumer).accept(false);
-        verify(consumer).accept("-1.66667");
-        verify(consumer).accept("nan");
-        verify(consumer).accept("-inf");
-        verify(consumer).accept("-0");
-        verify(consumer).accept("123456789");
-        verify(consumer).accept("1.23e+08");
-        verify(consumer, times(2)).accept(null);
-        verify(response, times(18)).call(resultIsOk());
-    }
-
-    @Test
-    public void testArgumentConversionBoolean() {
-        final Consumer<Object> consumer = (Consumer<Object>) mock(Consumer.class);
-        Object target = new VariantConsumer(consumer);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod(
-                "consumeByte", new RemoteInvocationArgument[] {booleanArgument(true)}, response);
-        remoteObject.invokeMethod(
-                "consumeChar", new RemoteInvocationArgument[] {booleanArgument(true)}, response);
-        remoteObject.invokeMethod(
-                "consumeShort", new RemoteInvocationArgument[] {booleanArgument(true)}, response);
-        remoteObject.invokeMethod(
-                "consumeInt", new RemoteInvocationArgument[] {booleanArgument(true)}, response);
-        remoteObject.invokeMethod(
-                "consumeLong", new RemoteInvocationArgument[] {booleanArgument(true)}, response);
-        remoteObject.invokeMethod(
-                "consumeFloat", new RemoteInvocationArgument[] {booleanArgument(true)}, response);
-        remoteObject.invokeMethod(
-                "consumeDouble", new RemoteInvocationArgument[] {booleanArgument(true)}, response);
-        remoteObject.invokeMethod(
-                "consumeString", new RemoteInvocationArgument[] {booleanArgument(true)}, response);
-        remoteObject.invokeMethod(
-                "consumeString", new RemoteInvocationArgument[] {booleanArgument(false)}, response);
-        remoteObject.invokeMethod(
-                "consumeObjectArray",
-                new RemoteInvocationArgument[] {booleanArgument(true)},
-                response);
-        remoteObject.invokeMethod(
-                "consumeObject", new RemoteInvocationArgument[] {booleanArgument(true)}, response);
-
-        InOrder inOrder = inOrder(consumer);
-        inOrder.verify(consumer).accept((byte) 0);
-        inOrder.verify(consumer).accept('\u0000');
-        inOrder.verify(consumer).accept((short) 0);
-        inOrder.verify(consumer).accept((int) 0);
-        inOrder.verify(consumer).accept((long) 0);
-        inOrder.verify(consumer).accept((float) 0);
-        inOrder.verify(consumer).accept((double) 0);
-        inOrder.verify(consumer).accept("true");
-        inOrder.verify(consumer).accept("false");
-        inOrder.verify(consumer, times(2)).accept(null);
-    }
-
-    @Test
-    public void testArgumentConversionString() {
-        final Consumer<Object> consumer = (Consumer<Object>) mock(Consumer.class);
-        Object target = new VariantConsumer(consumer);
-        String stringWithNonAsciiCharacterAndUnpairedSurrogate = "caf\u00e9\ud800";
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod(
-                "consumeByte", new RemoteInvocationArgument[] {stringArgument("hello")}, response);
-        remoteObject.invokeMethod(
-                "consumeChar", new RemoteInvocationArgument[] {stringArgument("hello")}, response);
-        remoteObject.invokeMethod(
-                "consumeShort", new RemoteInvocationArgument[] {stringArgument("hello")}, response);
-        remoteObject.invokeMethod(
-                "consumeInt", new RemoteInvocationArgument[] {stringArgument("hello")}, response);
-        remoteObject.invokeMethod(
-                "consumeLong", new RemoteInvocationArgument[] {stringArgument("hello")}, response);
-        remoteObject.invokeMethod(
-                "consumeFloat", new RemoteInvocationArgument[] {stringArgument("hello")}, response);
-        remoteObject.invokeMethod(
-                "consumeDouble",
-                new RemoteInvocationArgument[] {stringArgument("hello")},
-                response);
-        remoteObject.invokeMethod(
-                "consumeString",
-                new RemoteInvocationArgument[] {stringArgument("hello")},
-                response);
-        remoteObject.invokeMethod(
-                "consumeString",
-                new RemoteInvocationArgument[] {
-                    stringArgument(stringWithNonAsciiCharacterAndUnpairedSurrogate)
-                },
-                response);
-        remoteObject.invokeMethod(
-                "consumeObjectArray",
-                new RemoteInvocationArgument[] {stringArgument("hello")},
-                response);
-        remoteObject.invokeMethod(
-                "consumeObject",
-                new RemoteInvocationArgument[] {stringArgument("hello")},
-                response);
-
-        InOrder inOrder = inOrder(consumer);
-        inOrder.verify(consumer).accept((byte) 0);
-        inOrder.verify(consumer).accept('\u0000');
-        inOrder.verify(consumer).accept((short) 0);
-        inOrder.verify(consumer).accept((int) 0);
-        inOrder.verify(consumer).accept((long) 0);
-        inOrder.verify(consumer).accept((float) 0);
-        inOrder.verify(consumer).accept((double) 0);
-        inOrder.verify(consumer).accept("hello");
-        inOrder.verify(consumer).accept(stringWithNonAsciiCharacterAndUnpairedSurrogate);
-        inOrder.verify(consumer, times(2)).accept(null);
-    }
-
-    @Test
-    public void testArgumentConversionNull() {
-        final Consumer<Object> consumer = (Consumer<Object>) mock(Consumer.class);
-        Object target = new VariantConsumer(consumer);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        RemoteInvocationArgument args[] = {nullArgument()};
-        remoteObject.invokeMethod("consumeByte", args, response);
-        remoteObject.invokeMethod("consumeChar", args, response);
-        remoteObject.invokeMethod("consumeShort", args, response);
-        remoteObject.invokeMethod("consumeInt", args, response);
-        remoteObject.invokeMethod("consumeLong", args, response);
-        remoteObject.invokeMethod("consumeFloat", args, response);
-        remoteObject.invokeMethod("consumeDouble", args, response);
-        remoteObject.invokeMethod("consumeString", args, response);
-        remoteObject.invokeMethod("consumeObjectArray", args, response);
-        remoteObject.invokeMethod("consumeObject", args, response);
-
-        verify(consumer).accept((byte) 0);
-        verify(consumer).accept('\u0000');
-        verify(consumer).accept((short) 0);
-        verify(consumer).accept((int) 0);
-        verify(consumer).accept((long) 0);
-        verify(consumer).accept((float) 0);
-        verify(consumer).accept((double) 0);
-        verify(consumer, times(3)).accept(null);
-    }
-
-    @Test
-    public void testArgumentConversionUndefined() {
-        final Consumer<Object> consumer = (Consumer<Object>) mock(Consumer.class);
-        Object target = new VariantConsumer(consumer);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        RemoteInvocationArgument args[] = {undefinedArgument()};
-        remoteObject.invokeMethod("consumeByte", args, response);
-        remoteObject.invokeMethod("consumeChar", args, response);
-        remoteObject.invokeMethod("consumeShort", args, response);
-        remoteObject.invokeMethod("consumeInt", args, response);
-        remoteObject.invokeMethod("consumeLong", args, response);
-        remoteObject.invokeMethod("consumeFloat", args, response);
-        remoteObject.invokeMethod("consumeDouble", args, response);
-        remoteObject.invokeMethod("consumeString", args, response);
-        remoteObject.invokeMethod("consumeObjectArray", args, response);
-        remoteObject.invokeMethod("consumeObject", args, response);
-
-        verify(consumer).accept((byte) 0);
-        verify(consumer).accept('\u0000');
-        verify(consumer).accept((short) 0);
-        verify(consumer).accept((int) 0);
-        verify(consumer).accept((long) 0);
-        verify(consumer).accept((float) 0);
-        verify(consumer).accept((double) 0);
-        verify(consumer).accept("undefined");
-        verify(consumer, times(2)).accept(null);
-    }
-
-    @Test
-    public void testArgumentConversionArray() {
-        final Consumer<Object> consumer = (Consumer<Object>) mock(Consumer.class);
-        Object target = new VariantConsumer(consumer);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        RemoteInvocationArgument args[] = {
-            arrayArgument(
-                    numberArgument(3.14159),
-                    booleanArgument(true),
-                    stringArgument("Hello"),
-                    arrayArgument(),
-                    undefinedArgument())
-        };
-        remoteObject.invokeMethod("consumeByte", args, response);
-        remoteObject.invokeMethod("consumeChar", args, response);
-        remoteObject.invokeMethod("consumeShort", args, response);
-        remoteObject.invokeMethod("consumeInt", args, response);
-        remoteObject.invokeMethod("consumeLong", args, response);
-        remoteObject.invokeMethod("consumeFloat", args, response);
-        remoteObject.invokeMethod("consumeDouble", args, response);
-        remoteObject.invokeMethod("consumeBoolean", args, response);
-        remoteObject.invokeMethod("consumeString", args, response);
-        remoteObject.invokeMethod("consumeIntArray", args, response);
-        remoteObject.invokeMethod("consumeStringArray", args, response);
-        remoteObject.invokeMethod("consumeObjectArray", args, response);
-        remoteObject.invokeMethod("consumeObject", args, response);
-
-        verify(consumer).accept((byte) 0);
-        verify(consumer).accept('\u0000');
-        verify(consumer).accept((short) 0);
-        verify(consumer).accept((int) 0);
-        verify(consumer).accept((long) 0);
-        verify(consumer).accept((float) 0);
-        verify(consumer).accept((double) 0);
-        verify(consumer).accept(false);
-        verify(consumer).accept("undefined");
-        verify(consumer).accept(aryEq(new int[] {3, 0, 0, 0, 0}));
-        verify(consumer).accept(aryEq(new String[] {null, null, "Hello", null, null}));
-        verify(consumer, times(2)).accept(null);
-    }
-
-    @Test
-    public void testArgumentConversionTypedIntArray() {
-        final Consumer<Object> consumer = (Consumer<Object>) mock(Consumer.class);
-        Object target = new VariantConsumer(consumer);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        RemoteInvocationArgument args[] = {
-            typedArrayArgument(
-                    RemoteArrayType.INT8_ARRAY,
-                    BigBufferUtil.createBigBufferFromBytes(new byte[] {3, 2, 1, 0}))
-        };
-        remoteObject.invokeMethod("consumeByte", args, response);
-        remoteObject.invokeMethod("consumeChar", args, response);
-        remoteObject.invokeMethod("consumeShort", args, response);
-        remoteObject.invokeMethod("consumeInt", args, response);
-        remoteObject.invokeMethod("consumeLong", args, response);
-        remoteObject.invokeMethod("consumeFloat", args, response);
-        remoteObject.invokeMethod("consumeDouble", args, response);
-        remoteObject.invokeMethod("consumeBoolean", args, response);
-        remoteObject.invokeMethod("consumeString", args, response);
-        remoteObject.invokeMethod("consumeBooleanArray", args, response);
-        remoteObject.invokeMethod("consumeIntArray", args, response);
-        remoteObject.invokeMethod("consumeFloatArray", args, response);
-        remoteObject.invokeMethod("consumeDoubleArray", args, response);
-        remoteObject.invokeMethod("consumeStringArray", args, response);
-        remoteObject.invokeMethod("consumeObjectArray", args, response);
-        remoteObject.invokeMethod("consumeObject", args, response);
-
-        verify(consumer).accept((byte) 0);
-        verify(consumer).accept('\u0000');
-        verify(consumer).accept((short) 0);
-        verify(consumer).accept((int) 0);
-        verify(consumer).accept((long) 0);
-        verify(consumer).accept((float) 0);
-        verify(consumer).accept((double) 0);
-        verify(consumer).accept(false);
-        verify(consumer).accept("undefined");
-        verify(consumer).accept(aryEq(new boolean[] {false, false, false, false}));
-        verify(consumer).accept(aryEq(new int[] {3, 2, 1, 0}));
-        verify(consumer).accept(aryEq(new float[] {3, 2, 1, 0}));
-        verify(consumer).accept(aryEq(new double[] {3, 2, 1, 0}));
-        verify(consumer).accept(aryEq(new String[] {null, null, null, null}));
-        verify(consumer, times(2)).accept(null);
-    }
-
-    @Test
-    public void testArgumentConversionTypedDoubleArray() {
-        final Consumer<Object> consumer = (Consumer<Object>) mock(Consumer.class);
-        Object target = new VariantConsumer(consumer);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        RemoteInvocationArgument args[] = {
-            typedArrayArgument(
-                    RemoteArrayType.FLOAT64_ARRAY,
-                    BigBufferUtil.createBigBufferFromBytes(
-                            new byte[] {51, 51, 51, 51, 51, 51, 36, 64}))
-        };
-
-        remoteObject.invokeMethod("consumeByte", args, response);
-        remoteObject.invokeMethod("consumeChar", args, response);
-        remoteObject.invokeMethod("consumeShort", args, response);
-        remoteObject.invokeMethod("consumeInt", args, response);
-        remoteObject.invokeMethod("consumeLong", args, response);
-        remoteObject.invokeMethod("consumeFloat", args, response);
-        remoteObject.invokeMethod("consumeDouble", args, response);
-        remoteObject.invokeMethod("consumeBoolean", args, response);
-        remoteObject.invokeMethod("consumeString", args, response);
-        remoteObject.invokeMethod("consumeBooleanArray", args, response);
-        remoteObject.invokeMethod("consumeIntArray", args, response);
-        remoteObject.invokeMethod("consumeFloatArray", args, response);
-        remoteObject.invokeMethod("consumeDoubleArray", args, response);
-        remoteObject.invokeMethod("consumeStringArray", args, response);
-        remoteObject.invokeMethod("consumeObjectArray", args, response);
-        remoteObject.invokeMethod("consumeObject", args, response);
-
-        verify(consumer).accept((byte) 0);
-        verify(consumer).accept('\u0000');
-        verify(consumer).accept((short) 0);
-        verify(consumer).accept((int) 0);
-        verify(consumer).accept((long) 0);
-        verify(consumer).accept((float) 0);
-        verify(consumer).accept((double) 0);
-        verify(consumer).accept(false);
-        verify(consumer).accept("undefined");
-        verify(consumer).accept(aryEq(new boolean[] {false}));
-        verify(consumer).accept(aryEq(new int[] {10}));
-        verify(consumer).accept(aryEq(new float[] {10.1f}));
-        verify(consumer).accept(aryEq(new double[] {10.1}));
-        verify(consumer).accept(aryEq(new String[] {null}));
-        verify(consumer, times(2)).accept(null);
-    }
-
-    @Test
-    public void testArgumentConversionObjectId() {
-        Object foo = new Object();
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public Object getFoo() {
-                        return foo;
-                    }
-
-                    @TestJavascriptInterface
-                    public boolean isFoo(Object object) {
-                        return foo == object;
-                    }
-
-                    @TestJavascriptInterface
-                    public int isNotFoo(int number) {
-                        return number;
-                    }
-                };
-        when(mIdAllocator.getObjectId(foo, TestJavascriptInterface.class)).thenReturn(42);
-        when(mIdAllocator.getObjectById(42)).thenReturn(foo);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("getFoo", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod(
-                "isFoo", new RemoteInvocationArgument[] {objectIdArgument(42)}, response);
-        remoteObject.invokeMethod(
-                "isFoo", new RemoteInvocationArgument[] {objectIdArgument(100)}, response);
-        remoteObject.invokeMethod(
-                "isNotFoo", new RemoteInvocationArgument[] {objectIdArgument(42)}, response);
-
-        verify(response).call(resultIsObject(42));
-        verify(response).call(resultIsBoolean(true));
-        verify(response).call(resultIsBoolean(false));
-        verify(response).call(resultIsNumber(0));
-    }
-
-    @Test
-    public void testObjectNonAssignableType() {
-        class CustomType {}
-        Object foo = new Object();
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public Object getFoo() {
-                        return foo;
-                    }
-
-                    @TestJavascriptInterface
-                    public boolean exposedNonAssignableTypeMethodWithBooleanObject(Boolean value) {
-                        return true;
-                    }
-
-                    @TestJavascriptInterface
-                    public boolean exposedNonAssignableTypeMethodWithCustomObject(
-                            CustomType custom) {
-                        return true;
-                    }
-                };
-        when(mIdAllocator.getObjectId(foo, TestJavascriptInterface.class)).thenReturn(42);
-        when(mIdAllocator.getObjectById(42)).thenReturn(foo);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("getFoo", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod(
-                "exposedNonAssignableTypeMethodWithBooleanObject",
-                new RemoteInvocationArgument[] {objectIdArgument(42)},
-                response);
-        remoteObject.invokeMethod(
-                "exposedNonAssignableTypeMethodWithCustomObject",
-                new RemoteInvocationArgument[] {objectIdArgument(42)},
-                response);
-
-        verify(response).call(resultIsObject(42));
-        verify(response, times(2)).call(resultHasError(RemoteInvocationError.NON_ASSIGNABLE_TYPES));
-    }
-
-    @Test
-    public void testResultConversionVoid() {
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public void returnsVoid() {}
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("returnsVoid", new RemoteInvocationArgument[] {}, response);
-
-        verify(response).call(resultIsUndefined());
-    }
-
-    @Test
-    public void testConversionResultNumber() {
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public int returnsInt() {
-                        return 42;
-                    }
-
-                    @TestJavascriptInterface
-                    public float returnsFloat() {
-                        return -1.5f;
-                    }
-
-                    @TestJavascriptInterface
-                    public char returnsChar() {
-                        return '\ufeed';
-                    }
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("returnsInt", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod("returnsFloat", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod("returnsChar", new RemoteInvocationArgument[] {}, response);
-
-        verify(response).call(resultIsNumber(42));
-        verify(response).call(resultIsNumber(-1.5f));
-        verify(response).call(resultIsNumber(0xfeed));
-    }
-
-    @Test
-    public void testConversionResultBoolean() {
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public boolean returnsTrue() {
-                        return true;
-                    }
-
-                    @TestJavascriptInterface
-                    public boolean returnsFalse() {
-                        return false;
-                    }
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("returnsTrue", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod("returnsFalse", new RemoteInvocationArgument[] {}, response);
-
-        InOrder inOrder = inOrder(response);
-        inOrder.verify(response).call(resultIsBoolean(true));
-        inOrder.verify(response).call(resultIsBoolean(false));
-    }
-
-    @Test
-    public void testConversionResultString() {
-        final String stringWithNonAsciiCharacterAndUnpairedSurrogate = "caf\u00e9\ud800";
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public String returnsHello() {
-                        return "Hello";
-                    }
-
-                    @TestJavascriptInterface
-                    public String returnsExoticString() {
-                        return stringWithNonAsciiCharacterAndUnpairedSurrogate;
-                    }
-
-                    @TestJavascriptInterface
-                    public String returnsNull() {
-                        return null;
-                    }
-                };
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("returnsHello", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod(
-                "returnsExoticString", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod("returnsNull", new RemoteInvocationArgument[] {}, response);
-
-        verify(response).call(resultIsString("Hello"));
-        verify(response).call(resultIsString(stringWithNonAsciiCharacterAndUnpairedSurrogate));
-        verify(response).call(resultIsUndefined());
-    }
-
-    @Test
-    public void testConversionResultObject() {
-        final Object foo = new Object();
-        Object target =
-                new Object() {
-                    @TestJavascriptInterface
-                    public Object getFoo() {
-                        return foo;
-                    }
-
-                    @TestJavascriptInterface
-                    public Object getNull() {
-                        return null;
-                    }
-                };
-
-        when(mIdAllocator.getObjectId(foo, TestJavascriptInterface.class)).thenReturn(42);
-
-        RemoteObject remoteObject = newRemoteObjectImpl(target, TestJavascriptInterface.class);
-        RemoteObject.InvokeMethod_Response response =
-                mock(RemoteObject.InvokeMethod_Response.class);
-        remoteObject.invokeMethod("getFoo", new RemoteInvocationArgument[] {}, response);
-        remoteObject.invokeMethod("getNull", new RemoteInvocationArgument[] {}, response);
-
-        verify(response).call(resultIsObject(42));
-        verify(response).call(resultIsNull());
-    }
-
-    private RemoteInvocationResult resultHasError(final int error) {
-        return ArgumentMatchers.argThat(result -> result.error == error);
-    }
-
-    private RemoteInvocationResult resultIsOk() {
-        return resultHasError(RemoteInvocationError.OK);
-    }
-
-    private RemoteInvocationResult resultIsUndefined() {
-        return and(
-                resultIsOk(),
-                ArgumentMatchers.argThat(
-                        result -> {
-                            return result.value != null
-                                    && result.value.which()
-                                            == RemoteInvocationResultValue.Tag.SingletonValue
-                                    && result.value.getSingletonValue()
-                                            == SingletonJavaScriptValue.UNDEFINED;
-                        }));
-    }
-
-    private RemoteInvocationResult resultIsNull() {
-        return and(
-                resultIsOk(),
-                ArgumentMatchers.argThat(
-                        result -> {
-                            return result.value != null
-                                    && result.value.which()
-                                            == RemoteInvocationResultValue.Tag.SingletonValue
-                                    && result.value.getSingletonValue()
-                                            == SingletonJavaScriptValue.NULL;
-                        }));
-    }
-
-    private RemoteInvocationResult resultIsNumber(final double numberValue) {
-        return and(
-                resultIsOk(),
-                ArgumentMatchers.argThat(
-                        result -> {
-                            return result.value != null
-                                    && result.value.which()
-                                            == RemoteInvocationResultValue.Tag.NumberValue
-                                    && result.value.getNumberValue() == numberValue;
-                        }));
-    }
-
-    private RemoteInvocationResult resultIsBoolean(final boolean booleanValue) {
-        return and(
-                resultIsOk(),
-                ArgumentMatchers.argThat(
-                        result -> {
-                            return result.value != null
-                                    && result.value.which()
-                                            == RemoteInvocationResultValue.Tag.BooleanValue
-                                    && result.value.getBooleanValue() == booleanValue;
-                        }));
-    }
-
-    private RemoteInvocationResult resultIsString(String stringValue) {
-        final short[] expectedData = new short[stringValue.length()];
-        for (int i = 0; i < expectedData.length; i++) {
-            expectedData[i] = (short) stringValue.charAt(i);
-        }
-        return and(
-                resultIsOk(),
-                ArgumentMatchers.argThat(
-                        result -> {
-                            return result.value != null
-                                    && result.value.which()
-                                            == RemoteInvocationResultValue.Tag.StringValue
-                                    && Arrays.equals(
-                                            result.value.getStringValue().data, expectedData);
-                        }));
-    }
-
-    private RemoteInvocationResult resultIsObject(final int objectId) {
-        return and(
-                resultIsOk(),
-                ArgumentMatchers.argThat(
-                        result -> {
-                            return result.value != null
-                                    && result.value.which()
-                                            == RemoteInvocationResultValue.Tag.ObjectId
-                                    && result.value.getObjectId() == objectId;
-                        }));
-    }
-
-    private RemoteInvocationArgument numberArgument(double numberValue) {
-        RemoteInvocationArgument argument = new RemoteInvocationArgument();
-        argument.setNumberValue(numberValue);
-        return argument;
-    }
-
-    private RemoteInvocationArgument booleanArgument(boolean booleanValue) {
-        RemoteInvocationArgument argument = new RemoteInvocationArgument();
-        argument.setBooleanValue(booleanValue);
-        return argument;
-    }
-
-    private RemoteInvocationArgument stringArgument(String stringValue) {
-        String16 string16 = new String16();
-        string16.data = new short[stringValue.length()];
-        for (int i = 0; i < stringValue.length(); i++) {
-            string16.data[i] = (short) stringValue.charAt(i);
-        }
-        RemoteInvocationArgument argument = new RemoteInvocationArgument();
-        argument.setStringValue(string16);
-        return argument;
-    }
-
-    private RemoteInvocationArgument nullArgument() {
-        RemoteInvocationArgument argument = new RemoteInvocationArgument();
-        argument.setSingletonValue(SingletonJavaScriptValue.NULL);
-        return argument;
-    }
-
-    private RemoteInvocationArgument undefinedArgument() {
-        RemoteInvocationArgument argument = new RemoteInvocationArgument();
-        argument.setSingletonValue(SingletonJavaScriptValue.UNDEFINED);
-        return argument;
-    }
-
-    private RemoteInvocationArgument objectIdArgument(int objectId) {
-        RemoteInvocationArgument argument = new RemoteInvocationArgument();
-        argument.setObjectIdValue(objectId);
-        return argument;
-    }
-
-    private RemoteInvocationArgument arrayArgument(RemoteInvocationArgument... elements) {
-        RemoteInvocationArgument argument = new RemoteInvocationArgument();
-        argument.setArrayValue(elements);
-        return argument;
-    }
-
-    private RemoteInvocationArgument typedArrayArgument(
-            int type, org.chromium.mojo_base.mojom.BigBuffer buffer) {
-        RemoteInvocationArgument argument = new RemoteInvocationArgument();
-        RemoteTypedArray typedArray = new RemoteTypedArray();
-        typedArray.type = type;
-        typedArray.buffer = buffer;
-        argument.setTypedArrayValue(typedArray);
-        return argument;
-    }
-
-    private RemoteObjectImpl newRemoteObjectImpl(
-            Object target, Class<? extends Annotation> annotation) {
-        return newRemoteObjectImpl(target, annotation, true);
-    }
-
-    private RemoteObjectImpl newRemoteObjectImpl(
-            Object target, Class<? extends Annotation> annotation, boolean allowInspection) {
-        return new RemoteObjectImpl(target, annotation, mAuditor, mIdAllocator, allowInspection);
-    }
-}
diff --git a/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/RemoteObjectRegistryTest.java b/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/RemoteObjectRegistryTest.java
deleted file mode 100644
index 8bb6b2d..0000000
--- a/content/public/android/junit/src/org/chromium/content/browser/remoteobjects/RemoteObjectRegistryTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.content.browser.remoteobjects;
-
-import static org.hamcrest.Matchers.isIn;
-import static org.hamcrest.Matchers.not;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.test.BaseRobolectricTestRunner;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/** Tests the object registry, which maintains bidirectional object/ID mappings. */
-@RunWith(BaseRobolectricTestRunner.class)
-public final class RemoteObjectRegistryTest {
-    @Test
-    public void testMaintainsRetainingSet() {
-        // This is how the registry is expected to keep itself alive, despite only being held weakly
-        // from its main consumer.
-        Set<RemoteObjectRegistry> retainingSet = new HashSet<>();
-        RemoteObjectRegistry registry = new RemoteObjectRegistry(retainingSet);
-        Assert.assertThat(registry, isIn(retainingSet));
-        registry.close();
-        Assert.assertThat(registry, not(isIn(retainingSet)));
-    }
-
-    @Test
-    public void testGetObjectId() {
-        // We should get an ID that can be used to retrieve the object.
-        Set<RemoteObjectRegistry> retainingSet = new HashSet<>();
-        RemoteObjectRegistry registry = new RemoteObjectRegistry(retainingSet);
-        Object o = new Object();
-        int id = registry.getObjectId(o, null);
-        Assert.assertSame(o, registry.getObjectById(id));
-    }
-
-    @Test
-    public void testGetObjectIdSame() {
-        // The ID should be the same if retrieved twice.
-        Set<RemoteObjectRegistry> retainingSet = new HashSet<>();
-        RemoteObjectRegistry registry = new RemoteObjectRegistry(retainingSet);
-        Object o = new Object();
-        int id = registry.getObjectId(o, null);
-        Assert.assertEquals(id, registry.getObjectId(o, null));
-    }
-
-    @Test
-    public void testGetObjectIdAfterRemoval() {
-        // It should still work if we have previously added and removed the object.
-        Set<RemoteObjectRegistry> retainingSet = new HashSet<>();
-        RemoteObjectRegistry registry = new RemoteObjectRegistry(retainingSet);
-        Object o = new Object();
-        int id = registry.getObjectId(o, null);
-        registry.unrefObjectById(id);
-        int id2 = registry.getObjectId(o, null);
-        Assert.assertSame(o, registry.getObjectById(id2));
-    }
-
-    @Test
-    public void testReturnsNullForNonExistentObject() {
-        Set<RemoteObjectRegistry> retainingSet = new HashSet<>();
-        RemoteObjectRegistry registry = new RemoteObjectRegistry(retainingSet);
-        Assert.assertNull(registry.getObjectById(123));
-    }
-}
diff --git a/content/public/browser/browsing_data_filter_builder.h b/content/public/browser/browsing_data_filter_builder.h
index ce121c5..63e1135 100644
--- a/content/public/browser/browsing_data_filter_builder.h
+++ b/content/public/browser/browsing_data_filter_builder.h
@@ -114,6 +114,11 @@
   // Returns true if we're an empty preserve list, where we delete everything.
   virtual bool MatchesAllOriginsAndDomains() = 0;
 
+  // Returns true if we're deleting everything or nearly everything -- the mode
+  // is kPreserve, we're not restricted to partitioned cookies, and no
+  // StorageKey is set.
+  virtual bool MatchesMostOriginsAndDomains() = 0;
+
   // Returns true if we're an empty delete list, where we delete nothing.
   virtual bool MatchesNothing() = 0;
 
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
index 46a2307..f9651ee99 100644
--- a/content/public/common/content_switches.h
+++ b/content/public/common/content_switches.h
@@ -141,7 +141,7 @@
 CONTENT_EXPORT extern const char kGpuProcess[];
 CONTENT_EXPORT extern const char kGpuSandboxStartEarly[];
 CONTENT_EXPORT extern const char kGpuStartupDialog[];
-extern const char kHideScrollbars[];
+CONTENT_EXPORT extern const char kHideScrollbars[];
 CONTENT_EXPORT extern const char kInProcessGPU[];
 CONTENT_EXPORT extern const char kIPCConnectionTimeout[];
 CONTENT_EXPORT extern const char kIsolateOrigins[];
diff --git a/content/public/test/android/BUILD.gn b/content/public/test/android/BUILD.gn
index bca2697..87ca0e0f 100644
--- a/content/public/test/android/BUILD.gn
+++ b/content/public/test/android/BUILD.gn
@@ -44,8 +44,6 @@
   ]
   srcjar_deps = [ ":content_test_jni" ]
   sources = [
-    "javatests/src/org/chromium/content_public/browser/test/ChildProcessAllocatorSettings.java",
-    "javatests/src/org/chromium/content_public/browser/test/ChildProcessAllocatorSettingsHook.java",
     "javatests/src/org/chromium/content_public/browser/test/ContentJUnit4ClassRunner.java",
     "javatests/src/org/chromium/content_public/browser/test/ContentJUnit4RunnerDelegate.java",
     "javatests/src/org/chromium/content_public/browser/test/NativeLibraryTestUtils.java",
diff --git a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ChildProcessAllocatorSettings.java b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ChildProcessAllocatorSettings.java
deleted file mode 100644
index fc22a71d..0000000
--- a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ChildProcessAllocatorSettings.java
+++ /dev/null
@@ -1,19 +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.
-
-package org.chromium.content_public.browser.test;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/** An annotation used in test to hard-code some of the ChildProcessAllocator settings */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface ChildProcessAllocatorSettings {
-    int sandboxedServiceCount() default -1;
-
-    String sandboxedServiceName() default "";
-}
diff --git a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ChildProcessAllocatorSettingsHook.java b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ChildProcessAllocatorSettingsHook.java
deleted file mode 100644
index 76c115b..0000000
--- a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ChildProcessAllocatorSettingsHook.java
+++ /dev/null
@@ -1,31 +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.
-
-package org.chromium.content_public.browser.test;
-
-import android.content.Context;
-
-import org.junit.runners.model.FrameworkMethod;
-
-import org.chromium.base.test.BaseJUnit4ClassRunner.TestHook;
-import org.chromium.content.browser.ChildProcessLauncherHelperImpl;
-
-/**
- * PreTestHook used to register the ChildProcessAllocatorSettings annotation.
- *
- * TODO(yolandyan): convert this to TestRule once content tests are changed JUnit4
- * */
-public final class ChildProcessAllocatorSettingsHook implements TestHook {
-    @Override
-    public void run(Context targetContext, FrameworkMethod testMethod) {
-        ChildProcessAllocatorSettings annotation =
-                testMethod.getAnnotation(ChildProcessAllocatorSettings.class);
-        if (annotation != null) {
-            ChildProcessLauncherHelperImpl.setSandboxServicesSettingsForTesting(
-                    /* factory= */ null,
-                    annotation.sandboxedServiceCount(),
-                    annotation.sandboxedServiceName());
-        }
-    }
-}
diff --git a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ContentJUnit4ClassRunner.java b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ContentJUnit4ClassRunner.java
index 8b5768e..09a80f44 100644
--- a/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ContentJUnit4ClassRunner.java
+++ b/content/public/test/android/javatests/src/org/chromium/content_public/browser/test/ContentJUnit4ClassRunner.java
@@ -32,6 +32,7 @@
         // Display ui scale-up on auto for tests by default, individual tests can restore this
         // scaling.
         DisplayUtil.setUiScalingFactorForAutomotiveForTesting(1.0f);
+        EmbeddedTestServer.initCerts();
     }
 
     @Override
@@ -43,15 +44,4 @@
                 new UiDisableIfSkipCheck(InstrumentationRegistry.getTargetContext()),
                 new GmsCoreVersionRestrictionSkipCheck(getApplication().getApplicationContext()));
     }
-
-    /** Change this static function to add default {@code PreTestHook}s. */
-    @Override
-    protected List<TestHook> getPreTestHooks() {
-        return addToList(super.getPreTestHooks(), new ChildProcessAllocatorSettingsHook());
-    }
-
-    @Override
-    protected List<ClassHook> getPreClassHooks() {
-        return addToList(super.getPreClassHooks(), EmbeddedTestServer.getPreClassHook());
-    }
 }
diff --git a/content/shell/android/javatests/src/org/chromium/content_shell_apk/ContentShellActivityTestRule.java b/content/shell/android/javatests/src/org/chromium/content_shell_apk/ContentShellActivityTestRule.java
index 53b6d5e..01fd2c9 100644
--- a/content/shell/android/javatests/src/org/chromium/content_shell_apk/ContentShellActivityTestRule.java
+++ b/content/shell/android/javatests/src/org/chromium/content_shell_apk/ContentShellActivityTestRule.java
@@ -228,18 +228,14 @@
     }
 
     public JavascriptInjector getJavascriptInjector() {
-        return getJavascriptInjector(false);
-    }
-
-    public JavascriptInjector getJavascriptInjector(boolean useMojo) {
-        return JavascriptInjector.fromWebContents(getWebContents(), useMojo);
+        return JavascriptInjector.fromWebContents(getWebContents());
     }
 
     /**
-     * Waits for the Active shell to finish loading.  This times out after
-     * WAIT_FOR_ACTIVE_SHELL_LOADING_TIMEOUT milliseconds and it shouldn't be used for long
-     * loading pages. Instead it should be used more for test initialization. The proper way
-     * to wait is to use a TestCallbackHelperContainer after the initial load is completed.
+     * Waits for the Active shell to finish loading. This times out after
+     * WAIT_FOR_ACTIVE_SHELL_LOADING_TIMEOUT milliseconds and it shouldn't be used for long loading
+     * pages. Instead it should be used more for test initialization. The proper way to wait is to
+     * use a TestCallbackHelperContainer after the initial load is completed.
      */
     public void waitForActiveShellToBeDoneLoading() {
         // Wait for the Content Shell to be initialized.
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 44858e45..2544f8c2 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -111,8 +111,8 @@
     "../browser/payments/stub_payment_credential.h",
     "../browser/preloading/prefetch/mock_prefetch_service_delegate.cc",
     "../browser/preloading/prefetch/mock_prefetch_service_delegate.h",
-    "../browser/preloading/prefetch/prefetch_test_utils.cc",
-    "../browser/preloading/prefetch/prefetch_test_utils.h",
+    "../browser/preloading/prefetch/prefetch_test_util_internal.cc",
+    "../browser/preloading/prefetch/prefetch_test_util_internal.h",
     "../browser/presentation/presentation_test_utils.cc",
     "../browser/presentation/presentation_test_utils.h",
     "../browser/private_aggregation/private_aggregation_test_utils.cc",
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 8c779705..e70ba5f 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -5840,6 +5840,7 @@
 data/cacheable.svg.mock-http-headers
 data/cacheable2.js
 data/cacheable2.js.mock-http-headers
+data/changing_color.html
 data/click-nocontent-link.html
 data/click-noreferrer-links.html
 data/client_redirect.html
diff --git a/content/test/data/changing_color.html b/content/test/data/changing_color.html
new file mode 100644
index 0000000..9695a92
--- /dev/null
+++ b/content/test/data/changing_color.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="viewport" content="width=device-width, height=device-height">
+</head>
+<body style="border: 0; margin: 0; padding: 0; overflow:hidden;">
+<a name="red">
+  <div style="height: 100vh; background-color:#FF0000;"></div>
+</a>
+<a name="green">
+    <div style="height: 100vh; background-color:#00FF00;"></div>
+</a>
+<a name="blue">
+    <div style="height: 100vh; background-color:#0000FF;"></div>
+</a>
+</body>
+</html>
diff --git a/device/fido/features.cc b/device/fido/features.cc
index 88196679..638a339 100644
--- a/device/fido/features.cc
+++ b/device/fido/features.cc
@@ -92,9 +92,9 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Not yet enabled by default.
-BASE_FEATURE(kWebAuthnGpmPin,
-             "WebAuthenticationGpmPin",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+const base::FeatureParam<bool> kWebAuthnGpmPin{
+    &kWebAuthnEnclaveAuthenticator, kWebAuthnGpmPinFeatureParameterName,
+    /*default_value=*/false};
 
 // Enabled in M118 on all platforms except ChromeOS. Enabled on M121 for
 // ChromeOS. Remove in or after M124.
diff --git a/device/fido/features.h b/device/fido/features.h
index eaa28f7..3ecb917 100644
--- a/device/fido/features.h
+++ b/device/fido/features.h
@@ -71,8 +71,9 @@
 BASE_DECLARE_FEATURE(kWebAuthnEnclaveAuthenticator);
 
 // Enable use of Google Password Manager PIN.
+const char kWebAuthnGpmPinFeatureParameterName[] = "WebAuthenticationGpmPin";
 COMPONENT_EXPORT(DEVICE_FIDO)
-BASE_DECLARE_FEATURE(kWebAuthnGpmPin);
+extern const base::FeatureParam<bool> kWebAuthnGpmPin;
 
 // Filter a priori discovered credentials on google.com to those that have a
 // user id that starts with "GOOGLE_ACCOUNT:".
diff --git a/docs/updater/user_manual.md b/docs/updater/user_manual.md
index 72a4d23..2751997 100644
--- a/docs/updater/user_manual.md
+++ b/docs/updater/user_manual.md
@@ -92,6 +92,26 @@
 make the system more resilient, apps may periodically repeat the installation
 and registration process.
 
+#### CRURegistration library
+
+An Objective-C library to perform these operations is in development. When
+available, applications will be able to initialize
+[`CRURegistration`](https://chromium.googlesource.com/chromium/src/+/main/chrome/updater/mac/client_lib)
+with their product IDs, then use `installUpdaterWithReply:` and
+`registerVersion:existenceCheckerPath:serverURLString:reply:` to install the
+updater (if needed) and register. These methods operate asynchronously using
+[`dispatch/dispatch.h`](https://developer.apple.com/documentation/dispatch/dispatch_queue)
+mechanisms. `CRURegistration` maintains an internal task queue, so clients can
+call `register...` immediately after `install...` without waiting for a result.
+
+`CRURegistration` uses the helpers and command line binaries documented above.
+To install the updater using `CRURegistration`, the updater must be embedded
+as a Helper as documented above.
+
+`CRURegistration` is designed to depend only on APIs published in macOS SDKs
+and compile as pure Objective-C (without requiring C++ support) so it can be
+dropped into projects without incurring Chromium dependencies.
+
 ## Uninstalling Applications and the Updater
 
 The updater will uninstall itself automatically when it has no applications to
diff --git a/extensions/browser/service_worker/service_worker_task_queue.cc b/extensions/browser/service_worker/service_worker_task_queue.cc
index cc8a687..e926bb5 100644
--- a/extensions/browser/service_worker/service_worker_task_queue.cc
+++ b/extensions/browser/service_worker/service_worker_task_queue.cc
@@ -135,17 +135,12 @@
     return browser_state_ == BrowserState::kStarted &&
            renderer_state_ == RendererState::kStarted && worker_id_.has_value();
   }
-  bool has_pending_tasks() const { return !pending_tasks_.empty(); }
-
  private:
   friend class ServiceWorkerTaskQueue;
 
   BrowserState browser_state_ = BrowserState::kInitial;
   RendererState renderer_state_ = RendererState::kInitial;
 
-  // Pending tasks that will be run once the worker becomes ready.
-  std::vector<PendingTask> pending_tasks_;
-
   // Contains the worker's WorkerId associated with this WorkerState, once we
   // have discovered info about the worker.
   std::optional<WorkerId> worker_id_;
@@ -233,11 +228,11 @@
   WorkerState* worker_state = GetWorkerState(context_id);
   DCHECK(worker_state);
   if (g_test_observer) {
+    std::vector<PendingTask>* tasks = pending_tasks(context_id);
     g_test_observer->DidStartWorkerFail(context_id.extension_id,
-                                        worker_state->pending_tasks_.size(),
-                                        status_code);
+                                        tasks ? tasks->size() : 0, status_code);
   }
-  worker_state->pending_tasks_.clear();
+  DeleteAllPendingTasks(context_id);
   // TODO(https://crbug/1062936): Needs more thought: extension would be in
   // perma-broken state after this as the registration wouldn't be stored if
   // this happens.
@@ -448,18 +443,14 @@
   const SequencedContextId context_id = {lazy_context_id.extension_id(),
                                          lazy_context_id.browser_context(),
                                          *activation_token};
-  WorkerState* worker_state = GetWorkerState(context_id);
-  DCHECK(worker_state);
-  auto& tasks = worker_state->pending_tasks_;
-  // worker_state->pending_tasks_ having tasks means the
-  // worker has been requested to start and hasn't started yet. So
-  // `tasks.empty()` `false` means the worker is starting. `tasks.empty()`
-  // `true` means that we don't know if the worker is started so we'll try to
-  // start it to ensure it'll be ready for the task. This efficiency relies on
-  // the assumption that only this boolean controls whether we request the
-  // worker to start below.
-  bool needs_start_worker = tasks.empty();
-  tasks.push_back(std::move(task));
+
+  // `HasPendingTasks(context_id)`  `true` means the worker is starting.
+  // `HasPendingTasks(context_id)` `false` means that we don't know if the
+  // worker is started so we'll try to start it to ensure it'll be ready for the
+  // task. This efficiency relies on the assumption that only this boolean
+  // controls whether we request the worker to start below.
+  const bool worker_starting = HasPendingTasks(context_id);
+  AddPendingTaskForContext(std::move(task), context_id);
 
   if (!base::Contains(worker_registered_, context_id)) {
     // If the worker hasn't finished registration, wait for it to complete. The
@@ -469,13 +460,12 @@
     return;
   }
 
-  // Start worker if there aren't any tasks to dispatch to the worker (with
-  // `context_id`) in progress. Otherwise, assume the presence of pending tasks
-  // means we've started the worker and our start worker callback will run the
-  // pending tasks for us later.
-  if (needs_start_worker) {
-    RunTasksAfterStartWorker(context_id);
+  if (worker_starting) {
+    // When the worker finishes starting, the task queue will run `task`.
+    return;
   }
+
+  RunTasksAfterStartWorker(context_id);
 }
 
 void ServiceWorkerTaskQueue::ActivateExtension(const Extension* extension) {
@@ -488,6 +478,7 @@
                                          activation_token};
   DCHECK(!base::Contains(worker_state_map_, context_id));
   worker_state_map_.try_emplace(context_id);
+  pending_tasks_map_.try_emplace(context_id);
 
   content::ServiceWorkerContext* service_worker_context =
       GetServiceWorkerContext(extension->id());
@@ -564,7 +555,7 @@
   WorkerState* worker_state = GetWorkerState(context_id);
   DCHECK(worker_state);
   // TODO(lazyboy): Run orphaned tasks with nullptr ContextInfo.
-  worker_state->pending_tasks_.clear();
+  pending_tasks_map_.erase(context_id);
   worker_state_map_.erase(context_id);
   worker_registered_.erase(context_id);
 
@@ -618,6 +609,37 @@
   }
 }
 
+std::vector<ServiceWorkerTaskQueue::PendingTask>*
+ServiceWorkerTaskQueue::pending_tasks(const SequencedContextId& context_id) {
+  return base::FindOrNull(pending_tasks_map_, context_id);
+}
+
+std::vector<ServiceWorkerTaskQueue::PendingTask>&
+ServiceWorkerTaskQueue::GetOrAddPendingTasks(
+    const SequencedContextId& context_id) {
+  return pending_tasks_map_[context_id];
+}
+
+void ServiceWorkerTaskQueue::AddPendingTaskForContext(
+    PendingTask&& pending_task,
+    const SequencedContextId& context_id) {
+  GetOrAddPendingTasks(context_id).push_back(std::move(pending_task));
+}
+
+void ServiceWorkerTaskQueue::DeleteAllPendingTasks(
+    const SequencedContextId& context_id) {
+  std::vector<PendingTask>* tasks = pending_tasks(context_id);
+  if (tasks) {
+    tasks->clear();
+  }
+}
+
+bool ServiceWorkerTaskQueue::HasPendingTasks(
+    const SequencedContextId& context_id) {
+  std::vector<PendingTask>* tasks = pending_tasks(context_id);
+  return tasks ? !tasks->empty() : false;
+}
+
 void ServiceWorkerTaskQueue::DidRegisterServiceWorker(
     const SequencedContextId& context_id,
     RegistrationReason reason,
@@ -674,7 +696,7 @@
   pending_registrations_.emplace(extension->id(),
                                  *GetCurrentActivationToken(extension->id()));
 
-  if (worker_state->has_pending_tasks()) {
+  if (HasPendingTasks(context_id)) {
     // TODO(lazyboy): If worker for |context_id| is already running, consider
     // not calling StartWorker. This should be straightforward now that service
     // worker's internal state is on the UI thread rather than the IO thread.
@@ -753,7 +775,7 @@
     return;
   }
 
-  // Running `pending_tasks_[context_id]` marks the completion of both
+  // Running the pending tasks below marks the completion of both
   // DidStartWorkerForScope and DidStartWorkerContext, change `browser_ready`
   // state of the worker so that new tasks can be queued up.
   worker_state->browser_state_ = BrowserState::kReady;
@@ -761,10 +783,9 @@
     g_test_observer->DidStartWorker(context_id.extension_id);
   }
 
-  DCHECK(worker_state->has_pending_tasks())
-      << "Worker ready, but no tasks to run!";
+  DCHECK(HasPendingTasks(context_id)) << "Worker ready, but no tasks to run!";
   std::vector<PendingTask> tasks;
-  std::swap(worker_state->pending_tasks_, tasks);
+  std::swap(GetOrAddPendingTasks(context_id), tasks);
   DCHECK(worker_state->worker_id_);
   const auto& worker_id = *worker_state->worker_id_;
   for (auto& task : tasks) {
@@ -874,8 +895,8 @@
   const SequencedContextId context_id = {lazy_context_id.extension_id(),
                                          lazy_context_id.browser_context(),
                                          *activation_token};
-  WorkerState* worker_state = GetWorkerState(context_id);
-  return worker_state ? worker_state->pending_tasks_.size() : 0;
+  std::vector<PendingTask>* tasks = pending_tasks(context_id);
+  return tasks ? tasks->size() : 0;
 }
 
 // static
diff --git a/extensions/browser/service_worker/service_worker_task_queue.h b/extensions/browser/service_worker/service_worker_task_queue.h
index 98acb8b..a3aaf469 100644
--- a/extensions/browser/service_worker/service_worker_task_queue.h
+++ b/extensions/browser/service_worker/service_worker_task_queue.h
@@ -43,9 +43,9 @@
 // C1) Registering and starting a background worker:
 //   Upon extension activation, this class registers the extension's
 //   background worker if necessary. After that, if it has queued up tasks
-//   in |pending_tasks_|, then it moves on to starting the worker. Registration
-//   and start are initiated from this class. Once started, the worker is
-//   considered browser process ready. These workers are stored in
+//   in |pending_tasks_map_|, then it moves on to starting the worker.
+//   Registration and start are initiated from this class. Once started, the
+//   worker is considered browser process ready. These workers are stored in
 //   |worker_state_map_| with |browser_ready| = false until we run tasks.
 //
 // C2) Listening for worker's state update from the renderer:
@@ -60,7 +60,7 @@
 //
 // Once a worker reaches readiness in both browser process
 // (DidStartWorkerForScope) and worker process (DidStartServiceWorkerContext),
-// we consider the worker to be ready to run tasks from |pending_tasks_|.
+// we consider the worker to be ready to run tasks from |pending_tasks_map_|.
 // Note that events from #C1 and #C2 are somewhat independent, e.g. it is
 // possible to see an Init state update from #C2 before #C1 has seen a start
 // worker completion.
@@ -69,10 +69,9 @@
 //   This class also assigns a unique activation token to an extension
 //   activation so that it can differentiate between two activations of a
 //   particular extension (e.g. reloading an extension can cause two
-//   activations). |pending_tasks_|, worker registration and start (#C1) have
-//   activation tokens attached to them.
-//   The activation expires upon extension deactivation, and tasks are dropped
-//   from |pending_tasks_|.
+//   activations). |pending_tasks_map_|, worker registration and start (#C1)
+//   have activation tokens attached to them. The activation expires upon
+//   extension deactivation, and tasks are dropped from |pending_tasks_map_|.
 //
 // TODO(lazyboy): Clean up queue when extension is unloaded/uninstalled.
 class ServiceWorkerTaskQueue
@@ -308,11 +307,41 @@
   // Emit histograms when we know we're going to start the worker.
   void EmitWorkerWillBeStartedHistograms(const ExtensionId& extension_id);
 
+  // Returns the pending tasks for the activated extension. This returns
+  // `nullptr` if the vector has not been created yet for `context_id`. Should
+  // return non-null after activating extension and before deactivating
+  // extension.
+  std::vector<PendingTask>* pending_tasks(const SequencedContextId& context_id);
+
+  // Returns the pending tasks for the activated extension. This creates an
+  // empty `std::vector<PendingTask>` for `context_id` if there is not one yet.
+  // TODO(crbug.com/40276609): Can we ensure `context_id` key has been set
+  // before this is called so we don't need to add it?
+  std::vector<ServiceWorkerTaskQueue::PendingTask>& GetOrAddPendingTasks(
+      const SequencedContextId& context_id);
+
+  // Adds a pending task for the activated extension.
+  void AddPendingTaskForContext(PendingTask&& pending_task,
+                                const SequencedContextId& context_id);
+
+  // Stop tracking any pending tasks for this `context_id` for the activated
+  // extension.
+  void DeleteAllPendingTasks(const SequencedContextId& context_id);
+
+  // Whether there are any pending tasks to run for the activated extension.
+  bool HasPendingTasks(const SequencedContextId& context_id);
+
   std::map<content::ServiceWorkerContext*, int> observing_worker_contexts_;
 
   // The state of worker of each activated extension.
   std::map<SequencedContextId, WorkerState> worker_state_map_;
 
+  // TODO(crbug.com/40276609): Do we need to track this by `SequencedContextId`
+  // or could we use `ExtensionId` instead?
+  // `PendingTasks` for the activated extension that will be run as soon as the
+  // worker is started and ready.
+  std::map<SequencedContextId, std::vector<PendingTask>> pending_tasks_map_;
+
   const raw_ptr<content::BrowserContext> browser_context_ = nullptr;
 
   // A map of Service Worker registrations if this instance is for an
diff --git a/gpu/command_buffer/service/dawn_platform.cc b/gpu/command_buffer/service/dawn_platform.cc
index 1aa9b7f..1045ddb 100644
--- a/gpu/command_buffer/service/dawn_platform.cc
+++ b/gpu/command_buffer/service/dawn_platform.cc
@@ -91,7 +91,8 @@
     const char* uma_prefix)
     : dawn_caching_interface_(std::move(dawn_caching_interface)),
       uma_prefix_(uma_prefix),
-      cache_counts_(base::MakeRefCounted<CacheCounts>()) {}
+      cache_counts_(base::MakeRefCounted<CacheCounts>()),
+      startup_time_(base::Time::Now()) {}
 
 DawnPlatform::~DawnPlatform() = default;
 
@@ -140,27 +141,29 @@
   return result;
 }
 
-void DawnPlatform::HistogramCustomCounts(const char* name,
-                                         int sample,
-                                         int min,
-                                         int max,
-                                         int bucketCount) {
-  std::string nameStr = name;
+void DawnPlatform::HistogramCacheCountHelper(std::string name,
+                                             int sample,
+                                             int min,
+                                             int max,
+                                             int bucketCount) {
   bool post_task = false;
-  bool is_cache = nameStr.find("Cache");
-  if (is_cache) {
-    if (nameStr.find("Hit")) {
+  if (name.find("Cache") != std::string::npos) {
+    if (name.find("Hit") != std::string::npos) {
       cache_counts_->cache_hit_count.fetch_add(1, std::memory_order_release);
-    } else if (nameStr.find("Miss")) {
+    } else if (name.find("Miss") != std::string::npos) {
       cache_counts_->cache_miss_count.fetch_add(1, std::memory_order_release);
     }
     post_task = cache_counts_->did_schedule_log.exchange(
                     true, std::memory_order_acq_rel) == false;
   }
-  base::UmaHistogramCustomCounts(uma_prefix_ + nameStr, sample, min, max,
-                                 bucketCount);
 
   if (post_task) {
+    if ((base::Time::Now() - startup_time_).InSeconds() <= 90) {
+      base::UmaHistogramCustomCounts(
+          uma_prefix_ + name + ".90SecondsPostStartup", sample, min, max,
+          bucketCount);
+    }
+
     // Record the stats soonish after the first call.
     // The 90 seconds comes from the 99 percentile of startup time on macos.
     base::ThreadPool::PostDelayedTask(
@@ -168,22 +171,31 @@
         base::BindOnce(
             [](const std::string& name,
                scoped_refptr<CacheCounts> cache_counts) {
-              if (name.find("Hit")) {
-                UMA_HISTOGRAM_COUNTS_10000(name,
-                                           cache_counts->cache_hit_count.load(
-                                               std::memory_order_acquire));
+              if (name.find("Hit") != std::string::npos) {
+                base::UmaHistogramCounts10000(
+                    name, cache_counts->cache_hit_count.load(
+                              std::memory_order_acquire));
               } else {
-                UMA_HISTOGRAM_COUNTS_10000(name,
-                                           cache_counts->cache_miss_count.load(
-                                               std::memory_order_acquire));
+                base::UmaHistogramCounts10000(
+                    name, cache_counts->cache_miss_count.load(
+                              std::memory_order_acquire));
               }
             },
-            uma_prefix_ + nameStr + ".Counts.90SecondsPostStartup",
-            cache_counts_),
+            uma_prefix_ + name + ".Counts.90SecondsPostStartup", cache_counts_),
         base::Seconds(90));
   }
 }
 
+void DawnPlatform::HistogramCustomCounts(const char* name,
+                                         int sample,
+                                         int min,
+                                         int max,
+                                         int bucketCount) {
+  base::UmaHistogramCustomCounts(uma_prefix_ + name, sample, min, max,
+                                 bucketCount);
+  HistogramCacheCountHelper(name, sample, min, max, bucketCount);
+}
+
 void DawnPlatform::HistogramCustomCountsHPC(const char* name,
                                             int sample,
                                             int min,
@@ -192,6 +204,7 @@
   if (base::TimeTicks::IsHighResolution()) {
     base::UmaHistogramCustomCounts(uma_prefix_ + name, sample, min, max,
                                    bucketCount);
+    HistogramCacheCountHelper(name, sample, min, max, bucketCount);
   }
 }
 
diff --git a/gpu/command_buffer/service/dawn_platform.h b/gpu/command_buffer/service/dawn_platform.h
index 645e753..018a0274 100644
--- a/gpu/command_buffer/service/dawn_platform.h
+++ b/gpu/command_buffer/service/dawn_platform.h
@@ -5,10 +5,11 @@
 #ifndef GPU_COMMAND_BUFFER_SERVICE_DAWN_PLATFORM_H_
 #define GPU_COMMAND_BUFFER_SERVICE_DAWN_PLATFORM_H_
 
-#include <memory>
-
 #include <dawn/platform/DawnPlatform.h>
 
+#include <memory>
+
+#include "base/time/time.h"
 #include "gpu/command_buffer/service/dawn_caching_interface.h"
 
 namespace gpu::webgpu {
@@ -76,9 +77,16 @@
     ~CacheCounts();
   };
 
+  void HistogramCacheCountHelper(std::string name,
+                                 int sample,
+                                 int min,
+                                 int max,
+                                 int bucketCount);
+
   std::unique_ptr<DawnCachingInterface> dawn_caching_interface_ = nullptr;
   std::string uma_prefix_;
   scoped_refptr<CacheCounts> cache_counts_;
+  base::Time startup_time_;
 };
 
 }  // namespace gpu::webgpu
diff --git a/gpu/command_buffer/service/shared_image/shared_image_factory.cc b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
index 7cfc1e0..f065ee0 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
@@ -232,12 +232,6 @@
         auto supported_format = GetFormatPixmapSupport(supported_formats);
         base::UmaHistogramEnumeration("GPU.SharedImage.FormatPixmapSupport",
                                       supported_format);
-
-        // Check if hardware GMBs with RG88 format are ever created.
-        bool is_rg88_supported =
-            base::Contains(supported_formats, gfx::BufferFormat::RG_88);
-        base::UmaHistogramBoolean("GPU.SharedImage.IsRG88HardwareGMBSupported",
-                                  is_rg88_supported);
       }
     }
     set_format_supported_metric = true;
diff --git "a/infra/config/generated/builders/ci/Linux Builder \050dbg\051/properties.json" "b/infra/config/generated/builders/ci/Linux Builder \050dbg\051/properties.json"
index 9318a01d..6ee5ee2 100644
--- "a/infra/config/generated/builders/ci/Linux Builder \050dbg\051/properties.json"
+++ "b/infra/config/generated/builders/ci/Linux Builder \050dbg\051/properties.json"
@@ -26,6 +26,9 @@
                 "target_platform": "linux"
               },
               "legacy_gclient_config": {
+                "apply_configs": [
+                  "chromium_with_telemetry_dependencies"
+                ],
                 "config": "chromium"
               }
             }
diff --git "a/infra/config/generated/builders/ci/Linux Tests \050dbg\051\0501\051/properties.json" "b/infra/config/generated/builders/ci/Linux Tests \050dbg\051\0501\051/properties.json"
index 2321562..ac2f76d 100644
--- "a/infra/config/generated/builders/ci/Linux Tests \050dbg\051\0501\051/properties.json"
+++ "b/infra/config/generated/builders/ci/Linux Tests \050dbg\051\0501\051/properties.json"
@@ -23,6 +23,9 @@
                 "target_platform": "linux"
               },
               "legacy_gclient_config": {
+                "apply_configs": [
+                  "chromium_with_telemetry_dependencies"
+                ],
                 "config": "chromium"
               }
             }
diff --git a/infra/config/generated/builders/try/linux_chromium_compile_dbg_ng/properties.json b/infra/config/generated/builders/try/linux_chromium_compile_dbg_ng/properties.json
index df11509..afd5986b 100644
--- a/infra/config/generated/builders/try/linux_chromium_compile_dbg_ng/properties.json
+++ b/infra/config/generated/builders/try/linux_chromium_compile_dbg_ng/properties.json
@@ -26,6 +26,9 @@
                 "target_platform": "linux"
               },
               "legacy_gclient_config": {
+                "apply_configs": [
+                  "chromium_with_telemetry_dependencies"
+                ],
                 "config": "chromium"
               }
             }
diff --git a/infra/config/generated/builders/try/linux_chromium_dbg_ng/properties.json b/infra/config/generated/builders/try/linux_chromium_dbg_ng/properties.json
index 8919593..f2048c5 100644
--- a/infra/config/generated/builders/try/linux_chromium_dbg_ng/properties.json
+++ b/infra/config/generated/builders/try/linux_chromium_dbg_ng/properties.json
@@ -26,6 +26,9 @@
                 "target_platform": "linux"
               },
               "legacy_gclient_config": {
+                "apply_configs": [
+                  "chromium_with_telemetry_dependencies"
+                ],
                 "config": "chromium"
               }
             }
diff --git a/infra/config/generated/testing/mixins.pyl b/infra/config/generated/testing/mixins.pyl
index 7cb90d57..9d121ee 100644
--- a/infra/config/generated/testing/mixins.pyl
+++ b/infra/config/generated/testing/mixins.pyl
@@ -917,7 +917,7 @@
         'cpu': 'arm64',
         'gpu': 'apple:m2',
         'mac_model': 'Mac14,7',
-        'os': 'Mac-14.3.1|Mac-14.4.1',
+        'os': 'Mac-14.4.1',
         'pool': 'chromium.tests.gpu',
         'display_attached': '1',
         'hidpi': '1',
@@ -1006,7 +1006,7 @@
         'cpu': 'x86-64',
         'gpu': '1002:67ef',
         'hidpi': '1',
-        'os': 'Mac-14.3.1|Mac-14.4.1',
+        'os': 'Mac-14.4.1',
         'pool': 'chromium.tests.gpu',
         'display_attached': '1',
       },
@@ -1018,7 +1018,7 @@
         'cpu': 'x86-64',
         'gpu': '1002:67ef',
         'hidpi': '1',
-        'os': 'Mac-13.5',
+        'os': 'Mac-13.5|Mac-14.4.1',
         'pool': 'chromium.tests.gpu',
         'display_attached': '1',
       },
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index f04533d..51477e0 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -283,16 +283,16 @@
   },
   'LACROS_VERSION_SKEW_DEV': {
     'identifier': 'Lacros version skew testing ash dev',
-    'description': 'Run with ash-chrome version 126.0.6455.0',
+    'description': 'Run with ash-chrome version 126.0.6475.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6455.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v126.0.6455.0',
-          'revision': 'version:126.0.6455.0',
+          'location': 'lacros_version_skew_tests_v126.0.6475.0',
+          'revision': 'version:126.0.6475.0',
         },
       ],
     },
diff --git a/infra/config/subprojects/chromium/ci/chromium.linux.star b/infra/config/subprojects/chromium/ci/chromium.linux.star
index 5250c3b..2a685d96 100644
--- a/infra/config/subprojects/chromium/ci/chromium.linux.star
+++ b/infra/config/subprojects/chromium/ci/chromium.linux.star
@@ -247,6 +247,11 @@
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(
             config = "chromium",
+            apply_configs = [
+                # This is necessary due to child builders running the
+                # telemetry_perf_unittests suite.
+                "chromium_with_telemetry_dependencies",
+            ],
         ),
         chromium_config = builder_config.chromium_config(
             config = "chromium",
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index 27d65aa..fb6bbb6 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -17,16 +17,16 @@
   },
   "LACROS_VERSION_SKEW_DEV": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6455.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 126.0.6455.0",
+    "description": "Run with ash-chrome version 126.0.6475.0",
     "identifier": "Lacros version skew testing ash dev",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v126.0.6455.0",
-          "revision": "version:126.0.6455.0"
+          "location": "lacros_version_skew_tests_v126.0.6475.0",
+          "revision": "version:126.0.6475.0"
         }
       ]
     }
diff --git a/infra/config/targets/mixins.star b/infra/config/targets/mixins.star
index ce249a9..b028f85 100644
--- a/infra/config/targets/mixins.star
+++ b/infra/config/targets/mixins.star
@@ -1184,7 +1184,7 @@
             "cpu": "arm64",
             "gpu": "apple:m2",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "display_attached": "1",
             "hidpi": "1",
@@ -1298,7 +1298,7 @@
             "cpu": "x86-64",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "display_attached": "1",
         },
@@ -1312,7 +1312,7 @@
             "cpu": "x86-64",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "display_attached": "1",
         },
diff --git a/internal b/internal
index 284bc9f..88e16d7 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 284bc9fe6264205e4b08170f549ce8e1d32e3caf
+Subproject commit 88e16d7638ad0e2c73a70bbd704a06bacd4224ae
diff --git a/ios/build/tools/setup-gn.config b/ios/build/tools/setup-gn.config
index d38ca3ce4..96037d3 100644
--- a/ios/build/tools/setup-gn.config
+++ b/ios/build/tools/setup-gn.config
@@ -1,10 +1,3 @@
-[goma]
-# Controls whether goma is enabled or not. If you generally use goma but
-# want to disable goma for a single build, consider using the environment
-# variable GOMA_DISABLED.
-enabled = False
-install = "$GOMA_DIR"
-
 [xcode]
 # Controls settings for the generated Xcode project. If jobs is non-zero
 # it will be passed to the ninja invocation in Xcode project.
diff --git a/ios/build/tools/setup-gn.py b/ios/build/tools/setup-gn.py
index df8e9c08..bb0a31c0 100755
--- a/ios/build/tools/setup-gn.py
+++ b/ios/build/tools/setup-gn.py
@@ -116,12 +116,6 @@
     """
     args = []
 
-    if self._settings.getboolean('goma', 'enabled'):
-      args.append(('use_goma', True))
-      goma_dir = self._settings.getstring('goma', 'install')
-      if goma_dir:
-        args.append(('goma_dir', '"%s"' % os.path.expanduser(goma_dir)))
-
     is_debug = self._config == 'Debug'
     official = self._config == 'Official'
     is_optim = self._config in ('Profile', 'Official')
diff --git a/ios/build/tools/update_deps.py b/ios/build/tools/update_deps.py
index d5cf3ea..6c3b99c 100755
--- a/ios/build/tools/update_deps.py
+++ b/ios/build/tools/update_deps.py
@@ -34,10 +34,6 @@
 # The line containing the dependence in the error message.
 TRANSITIVE_DEPENDEE_LINE_NUMBER = 6
 
-# An error message that can be displayed at the beginning of the error.
-# This is supposed to be temporary and will be removed in 2024.
-TEMPORARY_ERROR = "The gn arg use_goma=true will be deprecated"
-
 MISSING_PATTERNS = [
     (3, "It is not in any dependency of"),
     (5, "The include file is in the target(s):"),
@@ -128,10 +124,8 @@
 def extract_missing_dependency(error, prefix, patterns, dependant_line,
                              dependee_line):
     """Parse gn error message for missing direct dependency."""
-    lines = [
-        line for line in error.splitlines()
-        if not line.startswith(TEMPORARY_ERROR)
-    ]
+    lines = error.splitlines()
+
     if len(lines) <= patterns[-1][0]:
         return False, None
     for line_number, pattern in patterns:
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index 2689853b..12a9e702 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -1580,6 +1580,26 @@
      flag_descriptions::kLensCircleToSearchEnabledName,
      flag_descriptions::kLensCircleToSearchEnabledDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kLensCircleToSearchEnabled)},
+    {"autofill-enable-save-card-loading-and-confirmation",
+     flag_descriptions::kAutofillEnableSaveCardLoadingAndConfirmationName,
+     flag_descriptions::
+         kAutofillEnableSaveCardLoadingAndConfirmationDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillEnableSaveCardLoadingAndConfirmation)},
+    {"autofill-enable-save-card-local-save-fallback",
+     flag_descriptions::kAutofillEnableSaveCardLocalSaveFallbackName,
+     flag_descriptions::kAutofillEnableSaveCardLocalSaveFallbackDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillEnableSaveCardLocalSaveFallback)},
+    {"autofill-enable-vcn-enroll-loading-and-confirmation",
+     flag_descriptions::kAutofillEnableVcnEnrollLoadingAndConfirmationName,
+     flag_descriptions::
+         kAutofillEnableVcnEnrollLoadingAndConfirmationDescription,
+     flags_ui::kOsIos,
+     FEATURE_VALUE_TYPE(
+         autofill::features::kAutofillEnableVcnEnrollLoadingAndConfirmation)},
 };
 
 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 d4be777..0108a02f 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -89,6 +89,23 @@
     "When enabled, some extra metrics logging for Autofill Downstream will "
     "start.";
 
+const char kAutofillEnableSaveCardLoadingAndConfirmationName[] =
+    "Enable save card loading and confirmation UX";
+const char kAutofillEnableSaveCardLoadingAndConfirmationDescription[] =
+    "When enabled, a loading spinner will be shown when uploading a card to "
+    "the server and a confirmation screen will be will be shown based on the "
+    "result of the upload. If the upload is unsuccessful in being uploaded to "
+    "the server, it will be saved locally.";
+
+const char kAutofillEnableSaveCardLocalSaveFallbackName[] =
+    "Enable save card local save fallback";
+const char kAutofillEnableSaveCardLocalSaveFallbackDescription[] =
+    "When enabled, if a card fails to be uploaded to the server, the card "
+    "details will be saved locally instead. If a card with the same card "
+    "number and expiration date already exists in the local database, this "
+    "will be a no-op and the existing card will not be updated with any card "
+    "details from the form.";
+
 const char kAutofillEnableSupportForAdminLevel2Name[] =
     "Enables parsing and filling of administrative area level 2 fields";
 const char kAutofillEnableSupportForAdminLevel2Description[] =
@@ -113,6 +130,14 @@
     "When enabled, card product name (instead of issuer network) will be shown "
     "in Payments UI.";
 
+const char kAutofillEnableVcnEnrollLoadingAndConfirmationName[] =
+    "Enable showing loading and confirmation screens for virtual card "
+    "enrollment";
+const char kAutofillEnableVcnEnrollLoadingAndConfirmationDescription[] =
+    "When enabled, the virtual card enrollment screen will present a loading "
+    "spinner while enrolling the card to the server and present a confirmation "
+    "screen with the result when completed.";
+
 const char kAutofillEnableVerveCardSupportName[] =
     "Enable autofill support for Verve cards";
 const char kAutofillEnableVerveCardSupportDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 1f0aedf..b9f226c 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -78,6 +78,16 @@
 extern const char kAutofillEnableRemadeDownstreamMetricsName[];
 extern const char kAutofillEnableRemadeDownstreamMetricsDescription[];
 
+// Title and description for the flag to enable loading and confirmation
+// for save card.
+extern const char kAutofillEnableSaveCardLoadingAndConfirmationName[];
+extern const char kAutofillEnableSaveCardLoadingAndConfirmationDescription[];
+
+// Title and description for the flag to enable fallback for save card failure
+// to upload and saves the card locally instead.
+extern const char kAutofillEnableSaveCardLocalSaveFallbackName[];
+extern const char kAutofillEnableSaveCardLocalSaveFallbackDescription[];
+
 // Title and description for the flag that controls whether Autofill handles
 // administrative area level 2 fields.
 extern const char kAutofillEnableSupportForAdminLevel2Name[];
@@ -98,6 +108,11 @@
 extern const char kAutofillEnableCardProductNameName[];
 extern const char kAutofillEnableCardProductNameDescription[];
 
+// Title and description for the flag to enable loading and confirmation
+// for virtual card enrollment.
+extern const char kAutofillEnableVcnEnrollLoadingAndConfirmationName[];
+extern const char kAutofillEnableVcnEnrollLoadingAndConfirmationDescription[];
+
 // Title and description for flag to enable Verve card support for autofill.
 extern const char kAutofillEnableVerveCardSupportName[];
 extern const char kAutofillEnableVerveCardSupportDescription[];
diff --git a/ios/chrome/browser/settings/model/sync/utils/sync_util.mm b/ios/chrome/browser/settings/model/sync/utils/sync_util.mm
index e0b3f0c..4605baf 100644
--- a/ios/chrome/browser/settings/model/sync/utils/sync_util.mm
+++ b/ios/chrome/browser/settings/model/sync/utils/sync_util.mm
@@ -300,11 +300,7 @@
 
     signin::IdentityManager* identityManager =
         IdentityManagerFactory::GetForBrowserState(browser_state);
-    // TODO(crbug.com/40066949): Simplify this (remove the whole
-    // `if (!UseIdentityErrorInfobar(syncService)) {...}`) after kSync users are
-    // migrated to kSignin in phase 3. See ConsentLevel::kSync documentation for
-    // details.
-    if (!identityManager->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
+    if (!identityManager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
       return false;
     }
   }
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_egtest.mm b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_egtest.mm
index d15da74..5e7aaaa 100644
--- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_egtest.mm
@@ -201,6 +201,17 @@
                       CardUnmaskPromptNavigationBarTitle()];
 }
 
+- (void)testCardUnmaskAuthenticationSelectionAcceptanceButtonIsSetInitially {
+  [self showAuthenticationSelection];
+
+  // Ensure the "Send" button is present (since the first option, OTP, is pre
+  // selected).
+  [[EarlGrey
+      selectElementWithMatcher:CardUnmaskAuthenticationSelectionSendButton()]
+      assertWithMatcher:grey_allOf(grey_enabled(), grey_sufficientlyVisible(),
+                                   nil)];
+}
+
 - (void)testCardUnmaskAuthenticationSelectionAcceptanceButtonLabel {
   [self showAuthenticationSelection];
 
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm
index b6a9ca1..c286ff3c1 100644
--- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator.mm
@@ -38,6 +38,9 @@
   [consumer_ setCardUnmaskOptions:ConvertChallengeOptions()];
   [consumer_ setFooterText:base::SysUTF16ToNSString(
                                model_controller_->GetContentFooterText())];
+  [consumer_
+      setChallengeAcceptanceLabel:base::SysUTF16ToNSString(
+                                      model_controller_->GetOkButtonLabel())];
 }
 
 CardUnmaskAuthenticationSelectionMediator::
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_unittest.mm b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_unittest.mm
index 68ecaddd..534f70c 100644
--- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_mediator_unittest.mm
@@ -116,7 +116,7 @@
 };
 
 TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
-       SetsHeaderTitleAndHeaderTextAndOptionsAndFooter) {
+       SetsHeaderTitleAndHeaderTextAndOptionsAndFooterAndChallengeAcceptance) {
   OCMExpect([consumer()
       setHeaderTitle:
           l10n_util::GetNSString(
@@ -132,12 +132,38 @@
       setFooterText:
           l10n_util::GetNSString(
               IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_CURRENT_INFO_NOT_SEEN_TEXT)]);
+  OCMExpect([consumer()
+      setChallengeAcceptanceLabel:
+          l10n_util::GetNSString(
+              IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_SEND)]);
 
   InitializeMediator(
       {SmsAutofillChallengeOption(), CvcAutofillChallengeOption()});
 }
 
 TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
+       SetsSendLabelInitiallyWhenSmsIsTheFirstChallengeOption) {
+  OCMExpect([consumer()
+      setChallengeAcceptanceLabel:
+          l10n_util::GetNSString(
+              IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_SEND)]);
+
+  InitializeMediator(
+      {SmsAutofillChallengeOption(), CvcAutofillChallengeOption()});
+}
+
+TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
+       SetsContinueLabelInitiallyWhenCvcIsTheFirstChallengeOption) {
+  OCMExpect([consumer()
+      setChallengeAcceptanceLabel:
+          l10n_util::GetNSString(
+              IDS_AUTOFILL_CARD_UNMASK_AUTHENTICATION_SELECTION_DIALOG_OK_BUTTON_LABEL_CONTINUE)]);
+
+  InitializeMediator(
+      {CvcAutofillChallengeOption(), SmsAutofillChallengeOption()});
+}
+
+TEST_F(CardUnmaskAuthenticationSelectionMediatorTest,
        OnDidSelectChallengeOption_SetsButtonLabel) {
   CardUnmaskAuthenticationSelectionMediator* mediator =
       InitializeMediator({SmsAutofillChallengeOption()});
diff --git a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_view_controller.mm b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_view_controller.mm
index dea21433..67ba9e13 100644
--- a/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_view_controller.mm
+++ b/ios/chrome/browser/ui/autofill/authentication/card_unmask_authentication_selection_view_controller.mm
@@ -29,7 +29,7 @@
   NSString* _headerText;
   NSString* _footerText;
   NSArray<CardUnmaskChallengeOptionIOS*>* _challengeOptions;
-  NSNumber* _selectedChallengeOptionIndex;
+  NSInteger _selectedChallengeOptionIndex;
   UITableViewDiffableDataSource<NSString*, NSNumber*>*
       _challengeOptionsDataSource;
 }
@@ -59,7 +59,7 @@
                           NSNumber* itemId) {
              return [weakSelf provideCellForTableView:tableView
                                             indexPath:indexPath
-                                 challengeOptionIndex:itemId];
+                                 challengeOptionIndex:itemId.integerValue];
            }];
   self.tableView.dataSource = _challengeOptionsDataSource;
   NSDiffableDataSourceSnapshot<NSString*, NSNumber*>* snapshot =
@@ -137,8 +137,7 @@
     didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
   // Set a checkmark on the cell when being selected.
   CHECK((NSUInteger)indexPath.row < [_challengeOptions count]);
-  [self setSelectedChallengeOptionIndex:[NSNumber
-                                            numberWithInteger:indexPath.row]];
+  [self setSelectedChallengeOptionIndex:indexPath.row];
   [self.mutator didSelectChallengeOption:_challengeOptions[indexPath.row]];
 }
 
@@ -166,17 +165,15 @@
 // Deques and sets up a cell for the challenge option at index.
 - (UITableViewCell*)provideCellForTableView:(UITableView*)tableView
                                   indexPath:(NSIndexPath*)indexPath
-                       challengeOptionIndex:(NSNumber*)challengeOptionIndex {
+                       challengeOptionIndex:(NSInteger)challengeOptionIndex {
   TableViewDetailIconCell* cell =
       DequeueTableViewCell<TableViewDetailIconCell>(self.tableView);
   cell.textLabel.text = _challengeOptions[indexPath.row].modeLabel;
   [cell setDetailText:_challengeOptions[indexPath.row].challengeInfo];
 
-  BOOL isSelected =
-      (_selectedChallengeOptionIndex != nil &&
-       [_selectedChallengeOptionIndex isEqualToNumber:challengeOptionIndex]);
-  [cell setAccessoryType:isSelected ? UITableViewCellAccessoryCheckmark
-                                    : UITableViewCellAccessoryNone];
+  [cell setAccessoryType:_selectedChallengeOptionIndex == challengeOptionIndex
+                             ? UITableViewCellAccessoryCheckmark
+                             : UITableViewCellAccessoryNone];
   [cell setTextLayoutConstraintAxis:UILayoutConstraintAxisVertical];
 
   [cell setDetailTextNumberOfLines:0];  // Use as many lines as needed.
@@ -204,19 +201,15 @@
 }
 
 // Sets the selected challenge option, updating the circle/checkmark icon.
-- (void)setSelectedChallengeOptionIndex:(NSNumber*)selectedIndex {
-  if ([_selectedChallengeOptionIndex isEqualToNumber:selectedIndex]) {
+- (void)setSelectedChallengeOptionIndex:(NSInteger)selectedIndex {
+  if (_selectedChallengeOptionIndex == selectedIndex) {
     return;
   }
   NSDiffableDataSourceSnapshot<NSString*, NSNumber*>* snapshot =
       [_challengeOptionsDataSource snapshot];
-  if (_selectedChallengeOptionIndex != nil) {
-    [snapshot reloadItemsWithIdentifiers:@[ _selectedChallengeOptionIndex ]];
-  }
+  [snapshot reloadItemsWithIdentifiers:@[ @(_selectedChallengeOptionIndex) ]];
   _selectedChallengeOptionIndex = selectedIndex;
-  if (_selectedChallengeOptionIndex != nil) {
-    [snapshot reloadItemsWithIdentifiers:@[ _selectedChallengeOptionIndex ]];
-  }
+  [snapshot reloadItemsWithIdentifiers:@[ @(_selectedChallengeOptionIndex) ]];
   [_challengeOptionsDataSource applySnapshotUsingReloadData:snapshot];
 }
 
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory/form_suggestion_label.mm b/ios/chrome/browser/ui/autofill/form_input_accessory/form_suggestion_label.mm
index f0df202..a6f9613 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory/form_suggestion_label.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory/form_suggestion_label.mm
@@ -147,14 +147,14 @@
         TextLabel(suggestionText, [UIColor colorNamed:kTextPrimaryColor], YES);
     valueLabel.font = [UIFont systemFontOfSize:valueLabel.font.pointSize
                                         weight:UIFontWeightMedium];
-    [stackView addArrangedSubview:valueLabel];
+    [stackView addArrangedSubview:[self splitLabel:valueLabel]];
 
     if ([suggestion.minorValue length] > 0) {
       UILabel* minorValueLabel = TextLabel(
           suggestion.minorValue, [UIColor colorNamed:kTextPrimaryColor], YES);
       minorValueLabel.font =
           [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2];
-      [stackView addArrangedSubview:minorValueLabel];
+      [stackView addArrangedSubview:[self splitLabel:minorValueLabel]];
     }
 
     if ([suggestion.displayDescription length] > 0) {
@@ -163,7 +163,7 @@
                     [UIColor colorNamed:kTextSecondaryColor], NO);
       description.font =
           [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2];
-      [stackView addArrangedSubview:description];
+      [stackView addArrangedSubview:[self splitLabel:description]];
     }
 
     [self setBackgroundColor:[self customBackgroundColor]];
@@ -300,4 +300,43 @@
   return maxWidth;
 }
 
+// Splits a credit card label into 2 labels, with one being an incompressible
+// credit card number label.
+- (UIView*)splitLabel:(UILabel*)label {
+  if (![self isCreditCardSuggestion]) {
+    return label;
+  }
+
+  // Look for a credit card number in the string. Note that U+202A is the
+  // "Left-to-right embedding" character and U+202C is the "Pop directional
+  // formatting" character. Credit card numbers are surrounded by these two
+  // Unicode characters.
+  NSRange range =
+      [label.text rangeOfString:@"\U0000202a•⁠ ⁠•⁠ ⁠"];
+  if (range.location == NSNotFound || range.location < 1) {
+    return label;
+  }
+
+  // Split the string in pre and post credit card number labels.
+  UILabel* creditCardLabel = [[UILabel alloc] init];
+  creditCardLabel.font = label.font;
+  creditCardLabel.text = [label.text substringFromIndex:range.location];
+  // The credit card number should not be compressible.
+  [creditCardLabel
+      setContentCompressionResistancePriority:UILayoutPriorityRequired
+                                      forAxis:UILayoutConstraintAxisHorizontal];
+
+  // Remove credit card number from the original string.
+  label.text = [label.text substringToIndex:range.location - 1];
+
+  // Stack both labels horizontally.
+  UIStackView* stackView = [[UIStackView alloc] initWithArrangedSubviews:@[]];
+  stackView.axis = UILayoutConstraintAxisHorizontal;
+  stackView.alignment = UIStackViewAlignmentCenter;
+  stackView.spacing = kSpacing;
+  [stackView addArrangedSubview:label];
+  [stackView addArrangedSubview:creditCardLabel];
+  return stackView;
+}
+
 @end
diff --git a/ios/web_view/tools/build.py b/ios/web_view/tools/build.py
index f24a6eb..296eecc 100755
--- a/ios/web_view/tools/build.py
+++ b/ios/web_view/tools/build.py
@@ -196,8 +196,6 @@
 
   parser.add_argument('out_dir', nargs='?', default='out/IOSWebViewBuild',
                       help='path to output directory')
-  parser.add_argument('--no_goma', action='store_true',
-                      help='Prevents adding use_goma=true to the gn args.')
   parser.add_argument('--ninja_args',
                       help='Additional gn args to pass through to ninja.')
   build_configs = ['Debug', 'Release']
@@ -224,8 +222,6 @@
 
   output_name = 'ChromeWebView'
   extra_gn_options = []
-  if not options.no_goma:
-    extra_gn_options.append('use_goma=true')
   # This prevents Breakpad from being included in the final binary to avoid
   # duplicate symbols with the client app.
   extra_gn_options.append('use_crash_key_stubs=true')
diff --git a/net/http/transport_security_state_static.pins b/net/http/transport_security_state_static.pins
index 8f23851..dbbe554 100644
--- a/net/http/transport_security_state_static.pins
+++ b/net/http/transport_security_state_static.pins
@@ -43,9 +43,9 @@
 #   hash function for preloaded entries again (we have already done so once).
 #
 
-# Last updated: 2024-05-15 12:57 UTC
+# Last updated: 2024-05-16 13:17 UTC
 PinsListTimestamp
-1715777868
+1715865434
 
 TestSPKI
 sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
diff --git a/net/http/transport_security_state_static_pins.json b/net/http/transport_security_state_static_pins.json
index b37456d..b7a55f0 100644
--- a/net/http/transport_security_state_static_pins.json
+++ b/net/http/transport_security_state_static_pins.json
@@ -31,7 +31,7 @@
 // the 'static_spki_hashes' and 'bad_static_spki_hashes' fields in 'pinsets'
 // refer to, and the timestamp at which the pins list was last updated.
 //
-// Last updated: 2024-05-15 12:57 UTC
+// Last updated: 2024-05-16 13:17 UTC
 //
 {
   "pinsets": [
diff --git a/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java b/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java
index 0b3d9979..e403d97 100644
--- a/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java
+++ b/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java
@@ -19,7 +19,6 @@
 import org.chromium.base.Log;
 import org.chromium.base.ResettersForTesting;
 import org.chromium.base.ThreadUtils;
-import org.chromium.base.test.BaseJUnit4ClassRunner.ClassHook;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.net.X509Util;
 import org.chromium.net.test.util.CertTestUtil;
@@ -523,11 +522,7 @@
         }
     }
 
-    public static ClassHook getPreClassHook() {
-        return (targetContext, testClass) -> EmbeddedTestServer.setUpClass(testClass);
-    }
-
-    public static void setUpClass(Class<?> clazz) {
+    public static void initCerts() {
         if (sTestRootInitDone) {
             return;
         }
diff --git a/net/test/embedded_test_server/http_response.h b/net/test/embedded_test_server/http_response.h
index f35a19f..97f275f 100644
--- a/net/test/embedded_test_server/http_response.h
+++ b/net/test/embedded_test_server/http_response.h
@@ -94,7 +94,11 @@
   void set_code(HttpStatusCode code) { code_ = code; }
 
   std::string reason() const {
-    return reason_.value_or(GetHttpReasonPhrase(code_));
+    if (reason_) {
+      return *reason_;
+    } else {
+      return GetHttpReasonPhrase(code_);
+    }
   }
   void set_reason(std::optional<std::string> reason) {
     reason_ = std::move(reason);
diff --git a/services/network/cookie_manager.cc b/services/network/cookie_manager.cc
index 9cd091b..fee9086 100644
--- a/services/network/cookie_manager.cc
+++ b/services/network/cookie_manager.cc
@@ -333,11 +333,6 @@
   cookie_settings_.set_block_third_party_cookies(block);
 }
 
-void CookieManager::BlockTruncatedCookies(bool block) {
-  OnSettingsWillChange();
-  cookie_settings_.set_block_truncated_cookies(block);
-}
-
 void CookieManager::SetMitigationsEnabledFor3pcd(bool enable) {
   OnSettingsWillChange();
   cookie_settings_.set_mitigations_enabled_for_3pcd(enable);
@@ -359,7 +354,6 @@
     const network::mojom::CookieManagerParams& params,
     CookieSettings* out) {
   out->set_block_third_party_cookies(params.block_third_party_cookies);
-  out->set_block_truncated_cookies(params.block_truncated_cookies);
   out->set_mitigations_enabled_for_3pcd(params.mitigations_enabled_for_3pcd);
   out->set_tracking_protection_enabled_for_3pcd(
       params.tracking_protection_enabled_for_3pcd);
diff --git a/services/network/cookie_manager.h b/services/network/cookie_manager.h
index 1518b88..71178430 100644
--- a/services/network/cookie_manager.h
+++ b/services/network/cookie_manager.h
@@ -114,7 +114,6 @@
                               AllowFileSchemeCookiesCallback callback) override;
   void SetForceKeepSessionState() override;
   void BlockThirdPartyCookies(bool block) override;
-  void BlockTruncatedCookies(bool block) override;
   void SetMitigationsEnabledFor3pcd(bool enable) override;
   void SetTrackingProtectionEnabledFor3pcd(bool enable) override;
 
diff --git a/services/network/cookie_settings.h b/services/network/cookie_settings.h
index dc11ffa5..24cba5e 100644
--- a/services/network/cookie_settings.h
+++ b/services/network/cookie_settings.h
@@ -90,10 +90,6 @@
   void set_content_settings(ContentSettingsType type,
                             const ContentSettingsForOneType& settings);
 
-  void set_block_truncated_cookies(bool block_truncated_cookies) {
-    block_truncated_cookies_ = block_truncated_cookies;
-  }
-
   void set_mitigations_enabled_for_3pcd(bool enable) {
     mitigations_enabled_for_3pcd_ = enable;
   }
@@ -106,10 +102,6 @@
     tpcd_metadata_manager_ = manager;
   }
 
-  bool are_truncated_cookies_blocked() const {
-    return block_truncated_cookies_;
-  }
-
   // Returns a predicate that takes the domain of a cookie and a bool whether
   // the cookie is secure and returns true if the cookie should be deleted on
   // exit.
@@ -238,7 +230,6 @@
   // Returns true if user blocks 3PC or 3PCD is on.
   bool block_third_party_cookies_ =
       net::cookie_util::IsForceThirdPartyCookieBlockingEnabled();
-  bool block_truncated_cookies_ = true;
   bool mitigations_enabled_for_3pcd_ = false;
   // This bool makes sure the correct cookie exclusion reasons are used.
   bool tracking_protection_enabled_for_3pcd_ = false;
diff --git a/services/network/public/mojom/cookie_manager.mojom b/services/network/public/mojom/cookie_manager.mojom
index 10d34fe..085af48 100644
--- a/services/network/public/mojom/cookie_manager.mojom
+++ b/services/network/public/mojom/cookie_manager.mojom
@@ -18,15 +18,6 @@
   // Whether or not third party cookies should be blocked.
   bool block_third_party_cookies = false;
 
-  // Whether or not truncated cookies should be ignored. Defaults to true but
-  // can be overridden in cases such as when the feature has been disabled via
-  // Enterprise Policy. Regardless of whether this value is true, the blocking
-  // of truncated cookies will not occur if the kBlockTruncatedCookies feature
-  // is disabled (thus, this default value is acceptable for WebView and other
-  // non-Chrome platforms where the feature flag is the primary means to control
-  // the value).
-  bool block_truncated_cookies = true;
-
   // Whether tracking protection for 3PCD (prefs + UX) is enabled.
   bool tracking_protection_enabled_for_3pcd = false;
 
@@ -491,11 +482,6 @@
   // Enables/Disables blocking of third-party cookies.
   BlockThirdPartyCookies(bool block);
 
-  // Enables/Disables blocking of truncated cookies. This provides a way to
-  // change the setting at runtime, which allows the corresponding enterprise
-  // policy to not require a restart to take affect.
-  BlockTruncatedCookies(bool block);
-
   // Enables/Disables mitigations for third-party cookies deprecation.
   SetMitigationsEnabledFor3pcd(bool enable);
 
diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc
index 52883c6..d2c94fe 100644
--- a/services/network/restricted_cookie_manager.cc
+++ b/services/network/restricted_cookie_manager.cc
@@ -955,8 +955,7 @@
       net::CanonicalCookie::Create(
           url, cookie, base::Time::Now(), /*server_time=*/std::nullopt,
           cookie_partition_key_,
-          cookie_settings().are_truncated_cookies_blocked(),
-          net::CookieSourceType::kScript, &status);
+          /*block_truncated=*/true, net::CookieSourceType::kScript, &status);
   if (!parsed_cookie) {
     if (cookie_observer_) {
       std::vector<network::mojom::CookieOrLineWithAccessResultPtr>
diff --git a/services/network/test/test_cookie_manager.h b/services/network/test/test_cookie_manager.h
index 1a165f2..8885311 100644
--- a/services/network/test/test_cookie_manager.h
+++ b/services/network/test/test_cookie_manager.h
@@ -61,7 +61,6 @@
       SetContentSettingsCallback callback) override {}
   void SetForceKeepSessionState() override {}
   void BlockThirdPartyCookies(bool block) override {}
-  void BlockTruncatedCookies(bool block) override {}
   void SetMitigationsEnabledFor3pcd(bool enable) override {}
   void SetTrackingProtectionEnabledFor3pcd(bool enable) override {}
   void SetPreCommitCallbackDelayForTesting(base::TimeDelta delay) override {}
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
index c9056af..3ede19b 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
@@ -23,6 +23,7 @@
 import "services/viz/public/mojom/compositing/copy_output_result.mojom";
 import "services/viz/public/mojom/compositing/video_detector_observer.mojom";
 import "third_party/blink/public/mojom/tokens/tokens.mojom";
+import "ui/gfx/geometry/mojom/geometry.mojom";
 
 // Initialization parameters for a RootCompositorFrameSink.
 struct RootCompositorFrameSinkParams {
@@ -220,6 +221,11 @@
   // testing only.
   [Sync]
   HasUnclaimedViewTransitionResourcesForTest() => (bool has_resources);
+
+  // Customizes the CopyOutputRequest's result size during tests.
+  [Sync]
+  SetSameDocNavigationScreenshotSizeForTesting(
+    gfx.mojom.Size result_size) => ();
 };
 
 // The FrameSinkManagerClient interface is implemented by the Display
diff --git a/services/webnn/tflite/graph_builder.cc b/services/webnn/tflite/graph_builder.cc
index b75b755..4340d34 100644
--- a/services/webnn/tflite/graph_builder.cc
+++ b/services/webnn/tflite/graph_builder.cc
@@ -1095,9 +1095,9 @@
 
 auto GraphBuilder::SerializeGather(const mojom::Gather& gather)
     -> base::expected<OperatorOffset, std::string> {
-  // The WebNN indices must be one of type uint32, int32, int64, but TFLite
-  // indices need int32 or int64 type, so a cast operation need to be inserted
-  // before Gather if indices data type is uint32.
+  // The WebNN indices must be one of type uint32 or int64, but TFLite indices
+  // need int32 or int64 type, so a cast operation need to be inserted before
+  // Gather if indices data type is uint32.
   int32_t indices_tensor_index =
       operand_to_index_map_.at(gather.indices_operand_id);
   const mojom::Operand& indices_operand = GetOperand(gather.indices_operand_id);
@@ -1112,8 +1112,7 @@
         /*input_tensor_type=*/::tflite::TensorType_UINT32, indices_tensor_index,
         /*output_tensor_type=*/::tflite::TensorType_INT64));
   } else {
-    CHECK(indices_operand.data_type == mojom::Operand::DataType::kInt64 ||
-          indices_operand.data_type == mojom::Operand::DataType::kInt32);
+    CHECK_EQ(indices_operand.data_type, mojom::Operand::DataType::kInt64);
   }
 
   // The WebNN axis option is uint32 data type, but TFLite axis needs int32
diff --git a/testing/buildbot/chromium.angle.json b/testing/buildbot/chromium.angle.json
index 7bca003..07ef80a 100644
--- a/testing/buildbot/chromium.angle.json
+++ b/testing/buildbot/chromium.angle.json
@@ -687,7 +687,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
@@ -717,7 +717,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -758,7 +758,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -796,7 +796,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -836,7 +836,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -874,7 +874,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -913,7 +913,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -951,7 +951,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -988,7 +988,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 65587cb..6162902 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5517,9 +5517,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5529,8 +5529,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
@@ -5673,9 +5673,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5685,8 +5685,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index 780c5ee..b59fc86d 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -19694,9 +19694,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19706,8 +19706,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
@@ -19850,9 +19850,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -19862,8 +19862,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.dawn.json b/testing/buildbot/chromium.dawn.json
index 745e858..b50eb075 100644
--- a/testing/buildbot/chromium.dawn.json
+++ b/testing/buildbot/chromium.dawn.json
@@ -5168,7 +5168,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5197,7 +5197,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5225,7 +5225,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5254,7 +5254,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5283,7 +5283,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5310,7 +5310,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5342,7 +5342,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5374,7 +5374,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5399,7 +5399,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5433,7 +5433,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5467,7 +5467,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5506,7 +5506,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5546,7 +5546,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5586,7 +5586,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5625,7 +5625,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5666,7 +5666,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5701,7 +5701,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5735,7 +5735,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5775,7 +5775,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -5818,7 +5818,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6541,7 +6541,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6569,7 +6569,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6596,7 +6596,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6624,7 +6624,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6652,7 +6652,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6678,7 +6678,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6709,7 +6709,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6740,7 +6740,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6764,7 +6764,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6797,7 +6797,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6830,7 +6830,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6867,7 +6867,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6907,7 +6907,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6941,7 +6941,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -6974,7 +6974,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -7013,7 +7013,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -7055,7 +7055,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -7605,7 +7605,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7634,7 +7634,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7662,7 +7662,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7691,7 +7691,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7720,7 +7720,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7747,7 +7747,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7779,7 +7779,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7811,7 +7811,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7836,7 +7836,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7870,7 +7870,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7904,7 +7904,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7942,7 +7942,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -7983,7 +7983,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -8018,7 +8018,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -8052,7 +8052,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -8092,7 +8092,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -8135,7 +8135,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -8207,7 +8207,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8235,7 +8235,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8262,7 +8262,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8290,7 +8290,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8318,7 +8318,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8344,7 +8344,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8375,7 +8375,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8406,7 +8406,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8430,7 +8430,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8463,7 +8463,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8496,7 +8496,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8533,7 +8533,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8573,7 +8573,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8607,7 +8607,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8640,7 +8640,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8679,7 +8679,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -8721,7 +8721,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
diff --git a/testing/buildbot/chromium.fuchsia.fyi.json b/testing/buildbot/chromium.fuchsia.fyi.json
index f74f702..9bb43995 100644
--- a/testing/buildbot/chromium.fuchsia.fyi.json
+++ b/testing/buildbot/chromium.fuchsia.fyi.json
@@ -1087,64 +1087,6 @@
       },
       {
         "args": [
-          "--num-retries=3",
-          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json"
-        ],
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "blink_web_tests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "dimensions": {
-            "cpu": "arm64",
-            "inside_docker": "1",
-            "os": "Ubuntu-22.04|Ubuntu-20.04"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 5
-        },
-        "test": "blink_web_tests",
-        "test_id_prefix": "ninja://:blink_web_tests/"
-      },
-      {
-        "args": [
-          "--num-retries=3",
-          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json"
-        ],
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "blink_wpt_tests",
-        "resultdb": {
-          "enable": true,
-          "has_native_resultdb_integration": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "dimensions": {
-            "cpu": "arm64",
-            "inside_docker": "1",
-            "os": "Ubuntu-22.04|Ubuntu-20.04"
-          },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 7
-        },
-        "test": "blink_wpt_tests",
-        "test_id_prefix": "ninja://:blink_wpt_tests/"
-      },
-      {
-        "args": [
           "context_lost",
           "--show-stdout",
           "--browser=web-engine-shell",
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 96bef93b9..258f85c 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -41869,9 +41869,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41880,8 +41880,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
@@ -42019,9 +42019,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42030,8 +42030,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
@@ -43369,9 +43369,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43381,8 +43381,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
@@ -43525,9 +43525,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43537,8 +43537,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
@@ -44849,9 +44849,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44860,8 +44860,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
@@ -44999,9 +44999,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_v126.0.6455.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -45010,8 +45010,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 5bbb93d..c7273757 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -12592,7 +12592,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12621,7 +12621,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12654,7 +12654,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12680,7 +12680,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12703,7 +12703,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12730,7 +12730,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12759,7 +12759,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12799,7 +12799,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12838,7 +12838,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12877,7 +12877,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12925,7 +12925,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -12973,7 +12973,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13021,7 +13021,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13060,7 +13060,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13099,7 +13099,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13142,7 +13142,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13190,7 +13190,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13238,7 +13238,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13286,7 +13286,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13326,7 +13326,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13366,7 +13366,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13406,7 +13406,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13445,7 +13445,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13484,7 +13484,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13523,7 +13523,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13562,7 +13562,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13603,7 +13603,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13646,7 +13646,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13687,7 +13687,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13729,7 +13729,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13770,7 +13770,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13810,7 +13810,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13839,7 +13839,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13869,7 +13869,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13903,7 +13903,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13930,7 +13930,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13954,7 +13954,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -13982,7 +13982,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14012,7 +14012,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14053,7 +14053,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14093,7 +14093,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14133,7 +14133,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14182,7 +14182,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14231,7 +14231,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14280,7 +14280,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14320,7 +14320,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14360,7 +14360,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14404,7 +14404,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14453,7 +14453,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14502,7 +14502,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14551,7 +14551,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14592,7 +14592,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14633,7 +14633,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14674,7 +14674,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14714,7 +14714,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14754,7 +14754,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14794,7 +14794,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14834,7 +14834,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14876,7 +14876,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14920,7 +14920,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -14962,7 +14962,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -15005,7 +15005,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -15047,7 +15047,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -15088,7 +15088,7 @@
             "gpu": "apple:m2",
             "hidpi": "1",
             "mac_model": "Mac14,7",
-            "os": "Mac-14.3.1|Mac-14.4.1",
+            "os": "Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "expiration": 21600,
@@ -17242,7 +17242,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17270,7 +17270,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17302,7 +17302,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17327,7 +17327,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17349,7 +17349,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17375,7 +17375,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17403,7 +17403,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17442,7 +17442,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17489,7 +17489,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17536,7 +17536,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17575,7 +17575,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17613,7 +17613,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17653,7 +17653,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17693,7 +17693,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17733,7 +17733,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17760,7 +17760,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17788,7 +17788,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17820,7 +17820,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17845,7 +17845,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17867,7 +17867,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17893,7 +17893,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17932,7 +17932,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -17979,7 +17979,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18017,7 +18017,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18055,7 +18055,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18097,7 +18097,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18144,7 +18144,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18183,7 +18183,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18221,7 +18221,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18260,7 +18260,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18288,7 +18288,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18316,7 +18316,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18348,7 +18348,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18373,7 +18373,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18395,7 +18395,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18421,7 +18421,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18449,7 +18449,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18488,7 +18488,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18526,7 +18526,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18573,7 +18573,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18620,7 +18620,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18658,7 +18658,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18696,7 +18696,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18743,7 +18743,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18790,7 +18790,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18829,7 +18829,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18868,7 +18868,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18906,7 +18906,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18944,7 +18944,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -18982,7 +18982,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -19022,7 +19022,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -19064,7 +19064,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -19104,7 +19104,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -19145,7 +19145,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
@@ -19184,7 +19184,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "hard_timeout": 1800,
diff --git a/testing/buildbot/chromium.gpu.json b/testing/buildbot/chromium.gpu.json
index efa428f..5e66c17 100644
--- a/testing/buildbot/chromium.gpu.json
+++ b/testing/buildbot/chromium.gpu.json
@@ -2152,7 +2152,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2181,7 +2181,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2214,7 +2214,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2254,7 +2254,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2302,7 +2302,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2341,7 +2341,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2380,7 +2380,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2423,7 +2423,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2471,7 +2471,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2511,7 +2511,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2550,7 +2550,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2591,7 +2591,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2619,7 +2619,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2648,7 +2648,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2681,7 +2681,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2710,7 +2710,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2750,7 +2750,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2798,7 +2798,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2837,7 +2837,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2876,7 +2876,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2919,7 +2919,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -2967,7 +2967,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -3007,7 +3007,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -3046,7 +3046,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
@@ -3087,7 +3087,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu",
             "puppet_env": "production"
           },
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 263da30..cdc0a9d 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -15796,12 +15796,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_v126.0.6455.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15811,8 +15811,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
@@ -15972,12 +15972,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_v126.0.6455.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 126.0.6455.0",
+        "description": "Run with ash-chrome version 126.0.6475.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -15987,8 +15987,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v126.0.6455.0",
-              "revision": "version:126.0.6455.0"
+              "location": "lacros_version_skew_tests_v126.0.6475.0",
+              "revision": "version:126.0.6475.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index 3fbf88f7..a466c82 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -306,14 +306,14 @@
           "can_use_on_swarming_builders": true,
           "dimensions": {
             "gpu": "10de",
-            "os": "Ubuntu-22.04",
+            "os": "Ubuntu",
             "pool": "chrome.tests.perf-fyi"
           },
           "expiration": 7200,
           "hard_timeout": 21600,
           "io_timeout": 21600,
           "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 1
+          "shards": 4
         },
         "test": "performance_test_suite",
         "trigger_script": {
diff --git a/testing/buildbot/filters/android.emulator_13.content_browsertests.filter b/testing/buildbot/filters/android.emulator_13.content_browsertests.filter
index 70cd81d..e9396dd4 100644
--- a/testing/buildbot/filters/android.emulator_13.content_browsertests.filter
+++ b/testing/buildbot/filters/android.emulator_13.content_browsertests.filter
@@ -45,9 +45,6 @@
 # crbug.com/340585286
 -FoldableAPIsOriginTrialBrowserTest.ValidOriginTrialToken
 
-# crbug.com/340585314
--AttributionInternalsWebUiBrowserTest.WebUIWithPendingReportsClearStorage_ReportsRemoved
-
 # crbug.com/340587779
 -PerformanceTimelineBrowserTest.NoResourceTimingEntryForFileProtocol
 
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 7cb90d57..9d121ee 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -917,7 +917,7 @@
         'cpu': 'arm64',
         'gpu': 'apple:m2',
         'mac_model': 'Mac14,7',
-        'os': 'Mac-14.3.1|Mac-14.4.1',
+        'os': 'Mac-14.4.1',
         'pool': 'chromium.tests.gpu',
         'display_attached': '1',
         'hidpi': '1',
@@ -1006,7 +1006,7 @@
         'cpu': 'x86-64',
         'gpu': '1002:67ef',
         'hidpi': '1',
-        'os': 'Mac-14.3.1|Mac-14.4.1',
+        'os': 'Mac-14.4.1',
         'pool': 'chromium.tests.gpu',
         'display_attached': '1',
       },
@@ -1018,7 +1018,7 @@
         'cpu': 'x86-64',
         'gpu': '1002:67ef',
         'hidpi': '1',
-        'os': 'Mac-13.5',
+        'os': 'Mac-13.5|Mac-14.4.1',
         'pool': 'chromium.tests.gpu',
         'display_attached': '1',
       },
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index fe2d06a7..9a1fa26b 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -327,6 +327,9 @@
   },
   'blink_web_tests': {
     'remove_from': [
+      # No arm64 apache suppport for fuchsia arm64 bots yet.
+      'fuchsia-arm64-cast-receiver-rel',
+      'fuchsia-fyi-arm64-dbg',
       'Win10 Tests x64 (dbg)',
       'win11-arm64-dbg-tests',
     ],
@@ -618,6 +621,9 @@
       'Win10 Tests x64 (dbg)',
       'devtools_frontend_linux_blink_light_rel',
       'devtools_frontend_linux_blink_light_rel_fastbuild',
+      # No arm64 apache suppport for fuchsia arm64 bots yet.
+      'fuchsia-arm64-cast-receiver-rel',
+      'fuchsia-fyi-arm64-dbg',
       'linux-rel-cft',
       'mac-rel-cft',
       'win-rel-cft',
diff --git a/testing/buildbot/tryserver.chromium.mac.json b/testing/buildbot/tryserver.chromium.mac.json
index 6fa2c7d..bbac3e9 100644
--- a/testing/buildbot/tryserver.chromium.mac.json
+++ b/testing/buildbot/tryserver.chromium.mac.json
@@ -17,7 +17,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -81,7 +81,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -143,7 +143,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
@@ -226,7 +226,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -296,7 +296,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -375,7 +375,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -463,7 +463,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -542,7 +542,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -612,7 +612,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -686,7 +686,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -809,7 +809,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -897,7 +897,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -977,7 +977,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -1049,7 +1049,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -1120,7 +1120,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -1226,7 +1226,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -1332,7 +1332,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -1402,7 +1402,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -1475,7 +1475,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -1551,7 +1551,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -1626,7 +1626,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
@@ -1699,7 +1699,7 @@
             "display_attached": "1",
             "gpu": "1002:67ef",
             "hidpi": "1",
-            "os": "Mac-13.5",
+            "os": "Mac-13.5|Mac-14.4.1",
             "pool": "chromium.tests.gpu"
           },
           "idempotent": false,
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index f04533d..51477e0 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -283,16 +283,16 @@
   },
   'LACROS_VERSION_SKEW_DEV': {
     'identifier': 'Lacros version skew testing ash dev',
-    'description': 'Run with ash-chrome version 126.0.6455.0',
+    'description': 'Run with ash-chrome version 126.0.6475.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6455.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v126.0.6475.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v126.0.6455.0',
-          'revision': 'version:126.0.6455.0',
+          'location': 'lacros_version_skew_tests_v126.0.6475.0',
+          'revision': 'version:126.0.6475.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 16621a9..d71fd68 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2310,6 +2310,28 @@
             ]
         }
     ],
+    "AvoidScheduleWorkDuringNativeEventProcessing": [
+        {
+            "platforms": [
+                "android_webview",
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AvoidScheduleWorkDuringNativeEventProcessing"
+                    ]
+                }
+            ]
+        }
+    ],
     "BFCachePerformanceManagerPolicy": [
         {
             "platforms": [
@@ -4647,6 +4669,23 @@
             ]
         }
     ],
+    "ComposeUiRefinement": [
+        {
+            "platforms": [
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "ComposeUiRefinement"
+                    ]
+                }
+            ]
+        }
+    ],
     "CompressionDictionaryTransport": [
         {
             "platforms": [
@@ -5829,12 +5868,12 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled1",
+                    "name": "V1Enabled2_20240516",
                     "params": {
-                        "group_name": "enabled-arm-1",
+                        "group_name": "enabled-v1-arm-2",
                         "max_prompt_count": "5",
                         "reprompt_duration": "7d",
-                        "reprompt_duration_multiplier ": "2",
+                        "reprompt_duration_multiplier": "2",
                         "show_app_menu_chip": "false",
                         "show_info_bar": "true",
                         "updated_info_bar_copy": "true"
@@ -16199,6 +16238,23 @@
             ]
         }
     ],
+    "PumpFastToSleepAndroid": [
+        {
+            "platforms": [
+                "android_weblayer",
+                "android_webview",
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PumpFastToSleepAndroid"
+                    ]
+                }
+            ]
+        }
+    ],
     "PushMessagingDisallowSenderIDs": [
         {
             "platforms": [
@@ -19952,6 +20008,24 @@
             ]
         }
     ],
+    "TabStateFlatBuffer": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_20240713",
+                    "params": {
+                        "migrate_stale_tabs": "false"
+                    },
+                    "enable_features": [
+                        "TabStateFlatBuffer"
+                    ]
+                }
+            ]
+        }
+    ],
     "TabStripStartupRefactoring": [
         {
             "platforms": [
diff --git a/third_party/blink/renderer/core/css/css_anchor_query_enums.h b/third_party/blink/renderer/core/css/css_anchor_query_enums.h
index db113d8..dc93709 100644
--- a/third_party/blink/renderer/core/css/css_anchor_query_enums.h
+++ b/third_party/blink/renderer/core/css/css_anchor_query_enums.h
@@ -20,6 +20,8 @@
     ~kCSSAnchorQueryTypesNone;
 
 enum class CSSAnchorValue {
+  kInside,
+  kOutside,
   kTop,
   kLeft,
   kRight,
diff --git a/third_party/blink/renderer/core/css/css_axis_value.h b/third_party/blink/renderer/core/css/css_axis_value.h
index bda8896..ce4c30c 100644
--- a/third_party/blink/renderer/core/css/css_axis_value.h
+++ b/third_party/blink/renderer/core/css/css_axis_value.h
@@ -9,6 +9,10 @@
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 namespace cssvalue {
 
@@ -17,7 +21,7 @@
   explicit CSSAxisValue(CSSValueID axis_name);
   CSSAxisValue(double x, double y, double z);
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   double X() const;
 
diff --git a/third_party/blink/renderer/core/css/css_border_image_slice_value.h b/third_party/blink/renderer/core/css/css_border_image_slice_value.h
index 6438b0c..afa4adf0 100644
--- a/third_party/blink/renderer/core/css/css_border_image_slice_value.h
+++ b/third_party/blink/renderer/core/css/css_border_image_slice_value.h
@@ -30,6 +30,10 @@
 #include "third_party/blink/renderer/core/css/css_quad_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 namespace cssvalue {
 
@@ -37,7 +41,7 @@
  public:
   CSSBorderImageSliceValue(CSSQuadValue* slices, bool fill);
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   // TODO(sashab): Change this to a quad of CSSPrimitiveValues, or add separate
   // methods for topSlice(), leftSlice(), etc.
diff --git a/third_party/blink/renderer/core/css/css_bracketed_value_list.h b/third_party/blink/renderer/core/css/css_bracketed_value_list.h
index ba34dc1..1ab4714 100644
--- a/third_party/blink/renderer/core/css/css_bracketed_value_list.h
+++ b/third_party/blink/renderer/core/css/css_bracketed_value_list.h
@@ -34,6 +34,10 @@
 #include "third_party/blink/renderer/core/css/css_value_list.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 namespace cssvalue {
 
@@ -41,7 +45,7 @@
  public:
   CSSBracketedValueList();
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   void TraceAfterDispatch(blink::Visitor* visitor) const {
     CSSValueList::TraceAfterDispatch(visitor);
diff --git a/third_party/blink/renderer/core/css/css_cursor_image_value.h b/third_party/blink/renderer/core/css/css_cursor_image_value.h
index 52acc89..11e4591 100644
--- a/third_party/blink/renderer/core/css/css_cursor_image_value.h
+++ b/third_party/blink/renderer/core/css/css_cursor_image_value.h
@@ -25,6 +25,10 @@
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 #include "ui/gfx/geometry/point.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 namespace cssvalue {
@@ -39,7 +43,7 @@
   const gfx::Point& HotSpot() const { return hot_spot_; }
   const CSSValue& ImageValue() const { return *image_value_; }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSCursorImageValue&) const;
 
diff --git a/third_party/blink/renderer/core/css/css_cyclic_variable_value.h b/third_party/blink/renderer/core/css/css_cyclic_variable_value.h
index aa29c36..cde7f8d 100644
--- a/third_party/blink/renderer/core/css/css_cyclic_variable_value.h
+++ b/third_party/blink/renderer/core/css/css_cyclic_variable_value.h
@@ -10,6 +10,10 @@
 #include "third_party/blink/renderer/core/css/css_invalid_variable_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSValuePool;
@@ -25,7 +29,7 @@
   explicit CSSCyclicVariableValue(base::PassKey<CSSValuePool>)
       : CSSInvalidVariableValue(kCyclicVariableValueClass) {}
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSCyclicVariableValue&) const { return true; }
 
diff --git a/third_party/blink/renderer/core/css/css_function_value.h b/third_party/blink/renderer/core/css/css_function_value.h
index f5c105b0..69b8c15 100644
--- a/third_party/blink/renderer/core/css/css_function_value.h
+++ b/third_party/blink/renderer/core/css/css_function_value.h
@@ -9,6 +9,10 @@
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSFunctionValue : public CSSValueList {
@@ -16,7 +20,7 @@
   CSSFunctionValue(CSSValueID id)
       : CSSValueList(kFunctionClass, kCommaSeparator), value_id_(id) {}
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSFunctionValue& other) const {
     return value_id_ == other.value_id_ && CSSValueList::Equals(other);
diff --git a/third_party/blink/renderer/core/css/css_grid_auto_repeat_value.h b/third_party/blink/renderer/core/css/css_grid_auto_repeat_value.h
index 008c6cf..d0e6757 100644
--- a/third_party/blink/renderer/core/css/css_grid_auto_repeat_value.h
+++ b/third_party/blink/renderer/core/css/css_grid_auto_repeat_value.h
@@ -9,6 +9,10 @@
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 namespace cssvalue {
 
@@ -33,7 +37,7 @@
     DCHECK(id == CSSValueID::kAutoFill || id == CSSValueID::kAutoFit);
   }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
   bool Equals(const CSSGridAutoRepeatValue&) const;
 
   CSSValueID AutoRepeatID() const { return auto_repeat_id_; }
diff --git a/third_party/blink/renderer/core/css/css_grid_integer_repeat_value.h b/third_party/blink/renderer/core/css/css_grid_integer_repeat_value.h
index c9ae067..8bcc4da8 100644
--- a/third_party/blink/renderer/core/css/css_grid_integer_repeat_value.h
+++ b/third_party/blink/renderer/core/css/css_grid_integer_repeat_value.h
@@ -10,6 +10,10 @@
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 namespace cssvalue {
 
@@ -29,7 +33,7 @@
     DCHECK_GT(repetitions, 0UL);
   }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
   bool Equals(const CSSGridIntegerRepeatValue&) const;
 
   wtf_size_t Repetitions() const { return repetitions_; }
diff --git a/third_party/blink/renderer/core/css/css_image_set_value.h b/third_party/blink/renderer/core/css/css_image_set_value.h
index 1d933940..4cf6250 100644
--- a/third_party/blink/renderer/core/css/css_image_set_value.h
+++ b/third_party/blink/renderer/core/css/css_image_set_value.h
@@ -31,6 +31,10 @@
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSImageSetOptionValue;
@@ -47,7 +51,7 @@
 
   const CSSImageSetOptionValue* GetBestOption(const float device_scale_factor);
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool HasFailedOrCanceledSubresources() const;
 
diff --git a/third_party/blink/renderer/core/css/css_inherited_value.h b/third_party/blink/renderer/core/css/css_inherited_value.h
index a5d10004..5ce377c 100644
--- a/third_party/blink/renderer/core/css/css_inherited_value.h
+++ b/third_party/blink/renderer/core/css/css_inherited_value.h
@@ -26,6 +26,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CORE_EXPORT CSSInheritedValue : public CSSValue {
@@ -36,7 +40,7 @@
   // Create() to get the pooled value.
   CSSInheritedValue() : CSSValue(kInheritedClass) {}
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSInheritedValue&) const { return true; }
 
diff --git a/third_party/blink/renderer/core/css/css_initial_color_value.h b/third_party/blink/renderer/core/css/css_initial_color_value.h
index 66be1f5..f3cc46c 100644
--- a/third_party/blink/renderer/core/css/css_initial_color_value.h
+++ b/third_party/blink/renderer/core/css/css_initial_color_value.h
@@ -10,6 +10,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSValuePool;
@@ -22,7 +26,7 @@
   explicit CSSInitialColorValue(base::PassKey<CSSValuePool>)
       : CSSValue(kInitialColorValueClass) {}
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSInitialColorValue&) const { return true; }
 
diff --git a/third_party/blink/renderer/core/css/css_initial_value.h b/third_party/blink/renderer/core/css/css_initial_value.h
index 0e2aebd..abddcf4 100644
--- a/third_party/blink/renderer/core/css/css_initial_value.h
+++ b/third_party/blink/renderer/core/css/css_initial_value.h
@@ -26,6 +26,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CORE_EXPORT CSSInitialValue : public CSSValue {
@@ -34,7 +38,7 @@
 
   CSSInitialValue() : CSSValue(kInitialClass) {}
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSInitialValue&) const { return true; }
 
diff --git a/third_party/blink/renderer/core/css/css_invalid_variable_value.h b/third_party/blink/renderer/core/css/css_invalid_variable_value.h
index a5b3f27..9e6e411 100644
--- a/third_party/blink/renderer/core/css/css_invalid_variable_value.h
+++ b/third_party/blink/renderer/core/css/css_invalid_variable_value.h
@@ -9,6 +9,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 // A value which represents custom properties that are invalid at computed-
@@ -23,7 +27,7 @@
   // Create() to get the pooled value.
   CSSInvalidVariableValue() : CSSValue(kInvalidVariableValueClass) {}
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSInvalidVariableValue&) const { return true; }
 
diff --git a/third_party/blink/renderer/core/css/css_layout_function_value.h b/third_party/blink/renderer/core/css/css_layout_function_value.h
index 18f0f0d..ec42c4e 100644
--- a/third_party/blink/renderer/core/css/css_layout_function_value.h
+++ b/third_party/blink/renderer/core/css/css_layout_function_value.h
@@ -9,6 +9,10 @@
 #include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSCustomIdentValue;
@@ -19,7 +23,7 @@
  public:
   CSSLayoutFunctionValue(CSSCustomIdentValue* name, bool is_inline);
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
   AtomicString GetName() const;
   bool IsInline() const { return is_inline_; }
 
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.cc b/third_party/blink/renderer/core/css/css_math_expression_node.cc
index 8b7f34e..220a6d98 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.cc
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.cc
@@ -2851,6 +2851,10 @@
 
 CSSAnchorValue CSSValueIDToAnchorValueEnum(CSSValueID value) {
   switch (value) {
+    case CSSValueID::kInside:
+      return CSSAnchorValue::kInside;
+    case CSSValueID::kOutside:
+      return CSSAnchorValue::kOutside;
     case CSSValueID::kTop:
       return CSSAnchorValue::kTop;
     case CSSValueID::kLeft:
@@ -3234,10 +3238,10 @@
     switch (anchor_query_type) {
       case CSSAnchorQueryType::kAnchor:
         value = css_parsing_utils::ConsumeIdent<
-            CSSValueID::kTop, CSSValueID::kLeft, CSSValueID::kRight,
-            CSSValueID::kBottom, CSSValueID::kStart, CSSValueID::kEnd,
-            CSSValueID::kSelfStart, CSSValueID::kSelfEnd, CSSValueID::kCenter>(
-            tokens);
+            CSSValueID::kInside, CSSValueID::kOutside, CSSValueID::kTop,
+            CSSValueID::kLeft, CSSValueID::kRight, CSSValueID::kBottom,
+            CSSValueID::kStart, CSSValueID::kEnd, CSSValueID::kSelfStart,
+            CSSValueID::kSelfEnd, CSSValueID::kCenter>(tokens);
         if (!value) {
           value = css_parsing_utils::ConsumePercent(
               tokens, context_, CSSPrimitiveValue::ValueRange::kAll);
diff --git a/third_party/blink/renderer/core/css/css_pending_system_font_value.h b/third_party/blink/renderer/core/css/css_pending_system_font_value.h
index bceb2452..ba6a980 100644
--- a/third_party/blink/renderer/core/css/css_pending_system_font_value.h
+++ b/third_party/blink/renderer/core/css/css_pending_system_font_value.h
@@ -9,6 +9,10 @@
 #include "third_party/blink/renderer/core/css_value_keywords.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 namespace cssvalue {
@@ -41,7 +45,7 @@
     return system_font_id_ == other.system_font_id_;
   }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   void TraceAfterDispatch(blink::Visitor*) const;
 
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 55ac796..4c7db34 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -320,13 +320,13 @@
         "border-radius",
         "border-outline-visited-color",
         "border-width",
+        "clip",
         "clip-path",
         "color",
         "compositing",
         "filter-data",
         "inset",
         "layout",
-        "list-item",
         "margin",
         "mask",
         "opacity",
@@ -335,10 +335,11 @@
         "paint",
         "reshape",
         "scroll-anchor",
+        "scrollbar-style",
         "stroke",
-        "table",
         "text-decoration",
         "transform-property",
+        "visibility",
         "visual-overflow",
         "z-index",
       ],
@@ -2121,7 +2122,7 @@
       },
       valid_for_permission_element: true,
       valid_for_page_context: true,
-      invalidate: ["border-radius"],
+      invalidate: ["border-radius", "paint"],
     },
     {
       name: "border-bottom-right-radius",
@@ -2143,7 +2144,7 @@
       },
       valid_for_permission_element: true,
       valid_for_page_context: true,
-      invalidate: ["border-radius"],
+      invalidate: ["border-radius", "paint"],
     },
     {
       name: "border-bottom-style",
@@ -2200,7 +2201,7 @@
       keywords: ["separate", "collapse"],
       typedom_types: ["Keyword"],
       default_value: "separate",
-      invalidate: ["table"],
+      invalidate: ["layout", "paint"],
     },
     {
       name: "border-image-outset",
@@ -2455,7 +2456,7 @@
       },
       valid_for_permission_element: true,
       valid_for_page_context: true,
-      invalidate: ["border-radius"],
+      invalidate: ["border-radius", "paint"],
     },
     {
       name: "border-top-right-radius",
@@ -2477,7 +2478,7 @@
       },
       valid_for_permission_element: true,
       valid_for_page_context: true,
-      invalidate: ["border-radius"],
+      invalidate: ["border-radius", "paint"],
     },
     {
       name: "border-top-style",
@@ -2635,7 +2636,7 @@
       keywords: ["top", "bottom"],
       typedom_types: ["Keyword"],
       default_value: "top",
-      invalidate: ["table"],
+      invalidate: ["layout", "paint"],
     },
     {
       name: "caret-color",
@@ -2679,6 +2680,7 @@
       converter: "ConvertClip",
       keywords: ["auto"],
       typedom_types: ["Keyword"],
+      invalidate: ["clip"],
     },
     {
       name: "clip-path",
@@ -3000,7 +3002,7 @@
       keywords: ["show", "hide"],
       typedom_types: ["Keyword"],
       default_value: "show",
-      invalidate: ["table"],
+      invalidate: ["layout", "paint"],
     },
     {
       name: "fill",
@@ -3598,7 +3600,7 @@
       keywords: ["outside", "inside"],
       typedom_types: ["Keyword"],
       default_value: "outside",
-      invalidate: ["list-item"],
+      invalidate: ["layout", "paint"],
     },
     {
       name: "list-style-type",
@@ -3616,7 +3618,7 @@
         "decimal", "none"
       ],
       style_builder_custom_functions: ["value"],
-      invalidate: ["list-item"],
+      invalidate: ["layout", "paint"],
     },
     {
       name: "margin-bottom",
@@ -4468,7 +4470,7 @@
       // have a priority higher than inset-area which is already at priority:1.
       priority: 2,
       valid_for_permission_element: true,
-      invalidate: ["layout", "scroll-anchor"],
+      invalidate: ["clip", "layout", "scroll-anchor"],
     },
     {
       name: "position-anchor",
@@ -4667,6 +4669,7 @@
       default_value: "nullptr",
       wrapper_pointer_name: "Member",
       include_paths: ["third_party/blink/renderer/core/style/style_scrollbar_color.h"],
+      invalidate: ["scrollbar-style"],
       runtime_flag: "ScrollbarColor",
     },
     {
@@ -4694,7 +4697,7 @@
       keywords: ["auto", "thin", "none"],
       default_value: "auto",
       typedom_types: ["Keyword"],
-      invalidate: ["layout", "paint"],
+      invalidate: ["layout", "paint", "scrollbar-style"],
       runtime_flag: "ScrollbarWidth",
     },
     {
@@ -5349,7 +5352,7 @@
       ],
       typedom_types: ["Keyword"],
       default_value: "auto",
-      invalidate: ["table"],
+      invalidate: ["layout", "paint"],
     },
     {
       name: "tab-size",
@@ -5968,7 +5971,7 @@
       valid_for_cue: true,
       valid_for_permission_element: true,
       valid_for_page_context: true,
-      invalidate: ["paint"],
+      invalidate: ["paint", "visibility"],
     },
     {
       name: "x",
diff --git a/third_party/blink/renderer/core/css/css_quad_value.h b/third_party/blink/renderer/core/css/css_quad_value.h
index 39df17e..897464a6 100644
--- a/third_party/blink/renderer/core/css/css_quad_value.h
+++ b/third_party/blink/renderer/core/css/css_quad_value.h
@@ -26,6 +26,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CORE_EXPORT CSSQuadValue : public CSSValue {
@@ -59,7 +63,7 @@
 
   TypeForSerialization SerializationType() { return serialization_type_; }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSQuadValue& other) const {
     return base::ValuesEquivalent(top_, other.top_) &&
diff --git a/third_party/blink/renderer/core/css/css_ray_value.h b/third_party/blink/renderer/core/css/css_ray_value.h
index 98eadbf..c7c51ae 100644
--- a/third_party/blink/renderer/core/css/css_ray_value.h
+++ b/third_party/blink/renderer/core/css/css_ray_value.h
@@ -8,6 +8,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSIdentifierValue;
@@ -29,7 +33,7 @@
   const CSSValue* CenterX() const { return center_x_.Get(); }
   const CSSValue* CenterY() const { return center_y_.Get(); }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSRayValue&) const;
 
diff --git a/third_party/blink/renderer/core/css/css_reflect_value.h b/third_party/blink/renderer/core/css/css_reflect_value.h
index 1b6e5749..2dc0e5d 100644
--- a/third_party/blink/renderer/core/css/css_reflect_value.h
+++ b/third_party/blink/renderer/core/css/css_reflect_value.h
@@ -30,6 +30,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSIdentifierValue;
@@ -51,7 +55,7 @@
   CSSPrimitiveValue* Offset() const { return offset_.Get(); }
   CSSValue* Mask() const { return mask_.Get(); }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSReflectValue&) const;
 
diff --git a/third_party/blink/renderer/core/css/css_revert_layer_value.h b/third_party/blink/renderer/core/css/css_revert_layer_value.h
index 650427b1..0b76df31 100644
--- a/third_party/blink/renderer/core/css/css_revert_layer_value.h
+++ b/third_party/blink/renderer/core/css/css_revert_layer_value.h
@@ -11,6 +11,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSValuePool;
@@ -24,7 +28,7 @@
   explicit CSSRevertLayerValue(base::PassKey<CSSValuePool>)
       : CSSValue(kRevertLayerClass) {}
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSRevertLayerValue&) const { return true; }
 
diff --git a/third_party/blink/renderer/core/css/css_revert_value.h b/third_party/blink/renderer/core/css/css_revert_value.h
index 065525e..96ef473d 100644
--- a/third_party/blink/renderer/core/css/css_revert_value.h
+++ b/third_party/blink/renderer/core/css/css_revert_value.h
@@ -11,6 +11,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSValuePool;
@@ -24,7 +28,7 @@
   explicit CSSRevertValue(base::PassKey<CSSValuePool>)
       : CSSValue(kRevertClass) {}
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSRevertValue&) const { return true; }
 
diff --git a/third_party/blink/renderer/core/css/css_scroll_value.h b/third_party/blink/renderer/core/css/css_scroll_value.h
index e10c6ca..3e30f42 100644
--- a/third_party/blink/renderer/core/css/css_scroll_value.h
+++ b/third_party/blink/renderer/core/css/css_scroll_value.h
@@ -9,6 +9,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 namespace cssvalue {
 
@@ -20,7 +24,7 @@
   const CSSValue* Scroller() const { return scroller_.Get(); }
   const CSSValue* Axis() const { return axis_.Get(); }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
   bool Equals(const CSSScrollValue&) const;
   void TraceAfterDispatch(blink::Visitor*) const;
 
diff --git a/third_party/blink/renderer/core/css/css_shadow_value.h b/third_party/blink/renderer/core/css/css_shadow_value.h
index 9e96305..d03a04c 100644
--- a/third_party/blink/renderer/core/css/css_shadow_value.h
+++ b/third_party/blink/renderer/core/css/css_shadow_value.h
@@ -26,6 +26,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSIdentifierValue;
@@ -41,7 +45,7 @@
                  CSSIdentifierValue* style,
                  CSSValue* color);
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSShadowValue&) const;
 
diff --git a/third_party/blink/renderer/core/css/css_unicode_range_value.h b/third_party/blink/renderer/core/css/css_unicode_range_value.h
index f49be2e..078b4c1 100644
--- a/third_party/blink/renderer/core/css/css_unicode_range_value.h
+++ b/third_party/blink/renderer/core/css/css_unicode_range_value.h
@@ -30,6 +30,10 @@
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_uchar.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 namespace cssvalue {
 
@@ -41,7 +45,7 @@
   UChar32 From() const { return from_; }
   UChar32 To() const { return to_; }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSUnicodeRangeValue&) const;
 
diff --git a/third_party/blink/renderer/core/css/css_unset_value.h b/third_party/blink/renderer/core/css/css_unset_value.h
index 8db2d62..648ef19 100644
--- a/third_party/blink/renderer/core/css/css_unset_value.h
+++ b/third_party/blink/renderer/core/css/css_unset_value.h
@@ -11,6 +11,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CSSValuePool;
@@ -23,7 +27,7 @@
 
   explicit CSSUnsetValue(base::PassKey<CSSValuePool>) : CSSValue(kUnsetClass) {}
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
 
   bool Equals(const CSSUnsetValue&) const { return true; }
 
diff --git a/third_party/blink/renderer/core/css/css_value.h b/third_party/blink/renderer/core/css/css_value.h
index c5488aa..93e14b94 100644
--- a/third_party/blink/renderer/core/css/css_value.h
+++ b/third_party/blink/renderer/core/css/css_value.h
@@ -31,8 +31,6 @@
 class String;
 }  // namespace WTF
 
-using WTF::String;
-
 namespace blink {
 
 class Document;
@@ -44,7 +42,7 @@
   // TODO(sashab): Remove this method and move logic to the caller.
   static CSSValue* Create(const Length& value, float zoom);
 
-  String CssText() const;
+  WTF::String CssText() const;
 
   bool IsNumericLiteralValue() const {
     return class_type_ == kNumericLiteralClass;
@@ -231,7 +229,7 @@
   bool IsScopedValue() const { return !needs_tree_scope_population_; }
 
 #if DCHECK_IS_ON()
-  String ClassTypeToString() const;
+  WTF::String ClassTypeToString() const;
 #endif
 
   void TraceAfterDispatch(blink::Visitor* visitor) const {}
diff --git a/third_party/blink/renderer/core/css/css_value_list.h b/third_party/blink/renderer/core/css/css_value_list.h
index da972d4..dca94840 100644
--- a/third_party/blink/renderer/core/css/css_value_list.h
+++ b/third_party/blink/renderer/core/css/css_value_list.h
@@ -27,6 +27,10 @@
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 
 class CORE_EXPORT CSSValueList : public CSSValue {
@@ -77,7 +81,7 @@
   bool HasValue(const CSSValue&) const;
   CSSValueList* Copy() const;
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
   bool Equals(const CSSValueList&) const;
 
   const CSSValueList& PopulateWithTreeScope(const TreeScope*) const;
diff --git a/third_party/blink/renderer/core/css/css_view_value.h b/third_party/blink/renderer/core/css/css_view_value.h
index 7c0c40a..39eb7d91 100644
--- a/third_party/blink/renderer/core/css/css_view_value.h
+++ b/third_party/blink/renderer/core/css/css_view_value.h
@@ -9,6 +9,10 @@
 #include "third_party/blink/renderer/core/css/css_value.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
+namespace WTF {
+class String;
+}  // namespace WTF
+
 namespace blink {
 namespace cssvalue {
 
@@ -20,7 +24,7 @@
   const CSSValue* Axis() const { return axis_.Get(); }
   const CSSValue* Inset() const { return inset_.Get(); }
 
-  String CustomCSSText() const;
+  WTF::String CustomCSSText() const;
   bool Equals(const CSSViewValue&) const;
   void TraceAfterDispatch(blink::Visitor*) const;
 
diff --git a/third_party/blink/renderer/core/css/cssom/css_url_image_value.h b/third_party/blink/renderer/core/css/cssom/css_url_image_value.h
index 8d7a894..f4f4ee1 100644
--- a/third_party/blink/renderer/core/css/cssom/css_url_image_value.h
+++ b/third_party/blink/renderer/core/css/cssom/css_url_image_value.h
@@ -29,7 +29,7 @@
       FlushReason,
       SourceImageStatus*,
       const gfx::SizeF&,
-      const AlphaDisposition alpha_disposition = kPremultiplyAlpha) final;
+      const AlphaDisposition alpha_disposition) final;
   bool IsAccelerated() const final;
 
   // CSSStyleValue
diff --git a/third_party/blink/renderer/core/css/invalidation/invalidation_set.cc b/third_party/blink/renderer/core/css/invalidation/invalidation_set.cc
index 56decaae2..2d9aea3 100644
--- a/third_party/blink/renderer/core/css/invalidation/invalidation_set.cc
+++ b/third_party/blink/renderer/core/css/invalidation/invalidation_set.cc
@@ -37,6 +37,7 @@
 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
+#include "third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
@@ -192,6 +193,7 @@
   }
 
   CHECK_NE(&other, this);
+  InvalidationSetToSelectorMap::CombineScope combine_scope(this, &other);
 
   if (auto* invalidation_set = DynamicTo<SiblingInvalidationSet>(this)) {
     SiblingInvalidationSet& siblings = *invalidation_set;
@@ -328,6 +330,9 @@
     return;
   }
   CHECK(!class_name.empty());
+  InvalidationSetToSelectorMap::RecordInvalidationSetEntry(
+      this, InvalidationSetToSelectorMap::SelectorFeatureType::kClass,
+      class_name);
   classes_.Add(backing_flags_, class_name);
 }
 
@@ -336,6 +341,8 @@
     return;
   }
   CHECK(!id.empty());
+  InvalidationSetToSelectorMap::RecordInvalidationSetEntry(
+      this, InvalidationSetToSelectorMap::SelectorFeatureType::kId, id);
   ids_.Add(backing_flags_, id);
 }
 
@@ -344,6 +351,9 @@
     return;
   }
   CHECK(!tag_name.empty());
+  InvalidationSetToSelectorMap::RecordInvalidationSetEntry(
+      this, InvalidationSetToSelectorMap::SelectorFeatureType::kTagName,
+      tag_name);
   tag_names_.Add(backing_flags_, tag_name);
 }
 
@@ -352,6 +362,9 @@
     return;
   }
   CHECK(!attribute.empty());
+  InvalidationSetToSelectorMap::RecordInvalidationSetEntry(
+      this, InvalidationSetToSelectorMap::SelectorFeatureType::kAttribute,
+      attribute);
   attributes_.Add(backing_flags_, attribute);
 }
 
diff --git a/third_party/blink/renderer/core/css/rule_set.cc b/third_party/blink/renderer/core/css/rule_set.cc
index b64fb89..2fb7c40b 100644
--- a/third_party/blink/renderer/core/css/rule_set.cc
+++ b/third_party/blink/renderer/core/css/rule_set.cc
@@ -50,6 +50,7 @@
 #include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
 #include "third_party/blink/renderer/core/html/track/text_track_cue.h"
 #include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.h"
 #include "third_party/blink/renderer/core/style/computed_style_constants.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
@@ -630,10 +631,14 @@
   RuleData rule_data(rule, selector_index, rule_count_, style_scope,
                      add_rule_flags, bloom_hash_backing_);
   ++rule_count_;
-  if (features_.CollectFeaturesFromSelector(rule_data.Selector(),
-                                            style_scope) ==
-      RuleFeatureSet::kSelectorNeverMatches) {
-    return;
+  {
+    InvalidationSetToSelectorMap::SelectorScope selector_scope(rule,
+                                                               selector_index);
+    if (features_.CollectFeaturesFromSelector(rule_data.Selector(),
+                                              style_scope) ==
+        RuleFeatureSet::kSelectorNeverMatches) {
+      return;
+    }
   }
 
   FindBestRuleSetAndAdd<BucketCoverage::kCompute>(rule_data.MutableSelector(),
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 3134e14..9a6cb37a 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -91,6 +91,7 @@
 #include "third_party/blink/renderer/core/html/track/text_track.h"
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.h"
 #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
 #include "third_party/blink/renderer/core/layout/geometry/logical_size.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_size.h"
@@ -741,6 +742,7 @@
   DCHECK(GetDocument().IsActive());
   DCHECK(IsMainThread());
   TRACE_EVENT0("blink", "Document::updateActiveStyle");
+  InvalidationSetToSelectorMap::StartOrStopTrackingIfNeeded();
   UpdateViewport();
   UpdateActiveStyleSheets();
   UpdateGlobalRuleSet();
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_image_source.h b/third_party/blink/renderer/core/html/canvas/canvas_image_source.h
index 87f6a9b..212e0da4 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_image_source.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_image_source.h
@@ -68,7 +68,7 @@
       FlushReason,
       SourceImageStatus*,
       const gfx::SizeF&,
-      const AlphaDisposition alpha_disposition = kPremultiplyAlpha) = 0;
+      const AlphaDisposition alpha_disposition) = 0;
 
   // IMPORTANT: Result must be independent of whether destinationContext is
   // already tainted because this function may be used to determine whether
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
index 9d6252b2..a042102 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.h
@@ -195,7 +195,7 @@
       FlushReason,
       SourceImageStatus*,
       const gfx::SizeF&,
-      const AlphaDisposition alpha_disposition = kPremultiplyAlpha) override;
+      const AlphaDisposition alpha_disposition) override;
   bool WouldTaintOrigin() const override;
   gfx::SizeF ElementSize(const gfx::SizeF&,
                          const RespectImageOrientationEnum) const override;
diff --git a/third_party/blink/renderer/core/html/canvas/image_element_base.h b/third_party/blink/renderer/core/html/canvas/image_element_base.h
index 064c91c..30991e7 100644
--- a/third_party/blink/renderer/core/html/canvas/image_element_base.h
+++ b/third_party/blink/renderer/core/html/canvas/image_element_base.h
@@ -37,7 +37,7 @@
       FlushReason,
       SourceImageStatus*,
       const gfx::SizeF&,
-      const AlphaDisposition alpha_disposition = kPremultiplyAlpha) override;
+      const AlphaDisposition alpha_disposition) override;
 
   bool WouldTaintOrigin() const override;
 
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.h b/third_party/blink/renderer/core/html/media/html_video_element.h
index cc1ea09..65b94ad 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.h
+++ b/third_party/blink/renderer/core/html/media/html_video_element.h
@@ -119,7 +119,7 @@
       FlushReason,
       SourceImageStatus*,
       const gfx::SizeF&,
-      const AlphaDisposition alpha_disposition = kPremultiplyAlpha) override;
+      const AlphaDisposition alpha_disposition) override;
   bool IsVideoElement() const override { return true; }
   bool WouldTaintOrigin() const override;
   gfx::SizeF ElementSize(const gfx::SizeF&,
diff --git a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
index fef5a81..2507d06 100644
--- a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
+++ b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc
@@ -658,8 +658,9 @@
                          std::optional<gfx::Rect> crop_rect,
                          const ImageBitmapOptions* options) {
   SourceImageStatus status;
-  scoped_refptr<Image> image_input = canvas->GetSourceImageForCanvas(
-      FlushReason::kCreateImageBitmap, &status, gfx::SizeF());
+  scoped_refptr<Image> image_input =
+      canvas->GetSourceImageForCanvas(FlushReason::kCreateImageBitmap, &status,
+                                      gfx::SizeF(), kPremultiplyAlpha);
   if (status != kNormalSourceImageStatus)
     return;
   DCHECK(IsA<StaticBitmapImage>(image_input.get()));
diff --git a/third_party/blink/renderer/core/imagebitmap/image_bitmap.h b/third_party/blink/renderer/core/imagebitmap/image_bitmap.h
index c00b5a25..b73ca58 100644
--- a/third_party/blink/renderer/core/imagebitmap/image_bitmap.h
+++ b/third_party/blink/renderer/core/imagebitmap/image_bitmap.h
@@ -112,7 +112,7 @@
       FlushReason,
       SourceImageStatus*,
       const gfx::SizeF&,
-      const AlphaDisposition alpha_disposition = kPremultiplyAlpha) override;
+      const AlphaDisposition alpha_disposition) override;
   bool WouldTaintOrigin() const override {
     return image_ ? !image_->OriginClean() : false;
   }
diff --git a/third_party/blink/renderer/core/inspector/build.gni b/third_party/blink/renderer/core/inspector/build.gni
index 2e9feeb..13062c25 100644
--- a/third_party/blink/renderer/core/inspector/build.gni
+++ b/third_party/blink/renderer/core/inspector/build.gni
@@ -106,6 +106,8 @@
   "inspector_task_runner.h",
   "inspector_trace_events.cc",
   "inspector_trace_events.h",
+  "invalidation_set_to_selector_map.cc",
+  "invalidation_set_to_selector_map.h",
   "legacy_dom_snapshot_agent.cc",
   "legacy_dom_snapshot_agent.h",
   "locale_controller.cc",
@@ -142,6 +144,7 @@
   "inspector_media_context_impl_unittest.cc",
   "inspector_session_state_test.cc",
   "inspector_style_resolver_test.cc",
+  "invalidation_set_to_selector_map_test.cc",
   "main_thread_debugger_test.cc",
   "protocol_parser_test.cc",
   "protocol_unittest.cc",
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
index 7a2a368..9d196b51 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/core/inspector/inspector_animation_agent.h"
 #include "third_party/blink/renderer/core/inspector/inspector_network_agent.h"
 #include "third_party/blink/renderer/core/inspector/inspector_page_agent.h"
+#include "third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.h"
 #include "third_party/blink/renderer/core/layout/hit_test_location.h"
 #include "third_party/blink/renderer/core/layout/hit_test_result.h"
 #include "third_party/blink/renderer/core/layout/layout_image.h"
@@ -634,6 +635,22 @@
   SetNodeInfo(dict, &node, "nodeId", "nodeName");
   dict.Add("reason", reason);
 }
+void FillSelectors(
+    perfetto::TracedDictionary& dict,
+    const InvalidationSet& invalidation_set,
+    InvalidationSetToSelectorMap::SelectorFeatureType feature_type,
+    const AtomicString& feature_value) {
+  const InvalidationSetToSelectorMap::IndexedSelectorList* selectors =
+      InvalidationSetToSelectorMap::Lookup(&invalidation_set, feature_type,
+                                           feature_value);
+  if (selectors != nullptr && selectors->size() > 0) {
+    dict.Add("selectorCount", selectors->size());
+    auto array = dict.AddArray("selectors");
+    for (auto selector : *selectors) {
+      array.Append(selector->GetSelectorText());
+    }
+  }
+}
 }  // namespace inspector_style_invalidator_invalidate_event
 
 void inspector_style_invalidator_invalidate_event::Data(
@@ -652,6 +669,24 @@
     const String& selector_part) {
   auto dict = std::move(context).WriteDictionary();
   FillCommonPart(dict, element, reason);
+  InvalidationSetToSelectorMap::SelectorFeatureType feature_type =
+      InvalidationSetToSelectorMap::SelectorFeatureType::kUnknown;
+  if (reason == kInvalidationSetMatchedClass) {
+    feature_type = InvalidationSetToSelectorMap::SelectorFeatureType::kClass;
+  } else if (reason == kInvalidationSetMatchedId) {
+    feature_type = InvalidationSetToSelectorMap::SelectorFeatureType::kId;
+  } else if (reason == kInvalidationSetMatchedTagName) {
+    feature_type = InvalidationSetToSelectorMap::SelectorFeatureType::kTagName;
+  } else if (reason == kInvalidationSetMatchedAttribute) {
+    feature_type =
+        InvalidationSetToSelectorMap::SelectorFeatureType::kAttribute;
+  }
+  if (feature_type !=
+      InvalidationSetToSelectorMap::SelectorFeatureType::kUnknown) {
+    FillSelectors(dict, invalidation_set, feature_type,
+                  AtomicString(selector_part));
+  }
+
   {
     auto array = dict.AddArray("invalidationList");
     array.Append(invalidation_set);
diff --git a/third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.cc b/third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.cc
new file mode 100644
index 0000000..4216e3f
--- /dev/null
+++ b/third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.cc
@@ -0,0 +1,207 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.h"
+
+#include "base/trace_event/trace_event.h"
+#include "third_party/blink/renderer/core/css/invalidation/invalidation_set.h"
+
+namespace blink {
+
+InvalidationSetToSelectorMap::IndexedSelector::IndexedSelector(
+    StyleRule* style_rule,
+    unsigned selector_index)
+    : style_rule_(style_rule), selector_index_(selector_index) {}
+
+void InvalidationSetToSelectorMap::IndexedSelector::Trace(
+    Visitor* visitor) const {
+  visitor->Trace(style_rule_);
+}
+
+StyleRule* InvalidationSetToSelectorMap::IndexedSelector::GetStyleRule() const {
+  return style_rule_;
+}
+
+unsigned InvalidationSetToSelectorMap::IndexedSelector::GetSelectorIndex()
+    const {
+  return selector_index_;
+}
+
+String InvalidationSetToSelectorMap::IndexedSelector::GetSelectorText() const {
+  return style_rule_->SelectorAt(selector_index_).SelectorText();
+}
+
+// static
+CORE_EXPORT void InvalidationSetToSelectorMap::StartOrStopTrackingIfNeeded() {
+  DEFINE_STATIC_LOCAL(
+      const unsigned char*, is_tracing_enabled,
+      (TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT(
+          "devtools.timeline.invalidationTracking"))));
+
+  Persistent<InvalidationSetToSelectorMap>& instance = GetInstanceReference();
+  if (*is_tracing_enabled && instance == nullptr) {
+    instance = MakeGarbageCollected<InvalidationSetToSelectorMap>();
+  } else if (!*is_tracing_enabled && instance != nullptr) {
+    instance.Clear();
+  }
+}
+
+// static
+void InvalidationSetToSelectorMap::BeginSelector(StyleRule* style_rule,
+                                                 unsigned selector_index) {
+  InvalidationSetToSelectorMap* instance = GetInstanceReference().Get();
+  if (instance == nullptr) {
+    return;
+  }
+
+  CHECK(instance->current_selector_ == nullptr);
+  instance->current_selector_ =
+      MakeGarbageCollected<IndexedSelector>(style_rule, selector_index);
+}
+
+// static
+void InvalidationSetToSelectorMap::EndSelector() {
+  InvalidationSetToSelectorMap* instance = GetInstanceReference().Get();
+  if (instance == nullptr) {
+    return;
+  }
+
+  CHECK(instance->current_selector_ != nullptr);
+  instance->current_selector_.Clear();
+}
+
+InvalidationSetToSelectorMap::SelectorScope::SelectorScope(
+    StyleRule* style_rule,
+    unsigned selector_index) {
+  InvalidationSetToSelectorMap::BeginSelector(style_rule, selector_index);
+}
+InvalidationSetToSelectorMap::SelectorScope::~SelectorScope() {
+  InvalidationSetToSelectorMap::EndSelector();
+}
+
+// static
+void InvalidationSetToSelectorMap::RecordInvalidationSetEntry(
+    const InvalidationSet* invalidation_set,
+    SelectorFeatureType type,
+    const AtomicString& value) {
+  InvalidationSetToSelectorMap* instance = GetInstanceReference().Get();
+  if (instance == nullptr) {
+    return;
+  }
+
+  // Ignore entries that get added during a combine operation. Those get
+  // handled when the combine operation begins.
+  if (instance->combine_recursion_depth_ > 0) {
+    return;
+  }
+
+  CHECK(instance->current_selector_ != nullptr);
+  InvalidationSetEntryMap* entry_map =
+      instance->invalidation_set_map_
+          ->insert(invalidation_set,
+                   MakeGarbageCollected<InvalidationSetEntryMap>())
+          .stored_value->value.Get();
+  IndexedSelectorList* indexed_selector_list =
+      entry_map
+          ->insert(InvalidationSetEntry(type, value),
+                   MakeGarbageCollected<IndexedSelectorList>())
+          .stored_value->value.Get();
+  indexed_selector_list->insert(instance->current_selector_);
+}
+
+// static
+void InvalidationSetToSelectorMap::BeginInvalidationSetCombine(
+    const InvalidationSet* target,
+    const InvalidationSet* source) {
+  InvalidationSetToSelectorMap* instance = GetInstanceReference().Get();
+  if (instance == nullptr) {
+    return;
+  }
+  instance->combine_recursion_depth_++;
+
+  // `source` may not be in the map if it contains only information that is not
+  // tracked such as self-invalidation, or if it was created before tracking
+  // started.
+  // TODO(crbug.com/337076014): Re-visit rule sets that already existed when
+  // tracking started so that invalidation sets for them can be included.
+  if (instance->invalidation_set_map_->Contains(source)) {
+    InvalidationSetEntryMap* target_entry_map =
+        instance->invalidation_set_map_
+            ->insert(target, MakeGarbageCollected<InvalidationSetEntryMap>())
+            .stored_value->value.Get();
+    auto source_entry_it = instance->invalidation_set_map_->find(source);
+    CHECK(source_entry_it != instance->invalidation_set_map_->end());
+    for (auto source_selector_list_it : *(source_entry_it->value)) {
+      IndexedSelectorList* target_selector_list =
+          target_entry_map
+              ->insert(source_selector_list_it.key,
+                       MakeGarbageCollected<IndexedSelectorList>())
+              .stored_value->value.Get();
+      for (auto source_selector : *(source_selector_list_it.value)) {
+        target_selector_list->insert(source_selector);
+      }
+    }
+  }
+}
+
+// static
+void InvalidationSetToSelectorMap::EndInvalidationSetCombine() {
+  InvalidationSetToSelectorMap* instance = GetInstanceReference().Get();
+  if (instance == nullptr) {
+    return;
+  }
+
+  CHECK_GT(instance->combine_recursion_depth_, 0u);
+  instance->combine_recursion_depth_--;
+}
+
+InvalidationSetToSelectorMap::CombineScope::CombineScope(
+    const InvalidationSet* target,
+    const InvalidationSet* source) {
+  InvalidationSetToSelectorMap::BeginInvalidationSetCombine(target, source);
+}
+
+InvalidationSetToSelectorMap::CombineScope::~CombineScope() {
+  InvalidationSetToSelectorMap::EndInvalidationSetCombine();
+}
+
+// static
+const InvalidationSetToSelectorMap::IndexedSelectorList*
+InvalidationSetToSelectorMap::Lookup(const InvalidationSet* invalidation_set,
+                                     SelectorFeatureType type,
+                                     const AtomicString& value) {
+  const InvalidationSetToSelectorMap* instance = GetInstanceReference().Get();
+  if (instance == nullptr) {
+    return nullptr;
+  }
+
+  auto entry_it = instance->invalidation_set_map_->find(invalidation_set);
+  if (entry_it != instance->invalidation_set_map_->end()) {
+    auto selector_list_it =
+        entry_it->value->find(InvalidationSetEntry(type, value));
+    if (selector_list_it != entry_it->value->end()) {
+      return selector_list_it->value;
+    }
+  }
+
+  return nullptr;
+}
+
+InvalidationSetToSelectorMap::InvalidationSetToSelectorMap() {
+  invalidation_set_map_ = MakeGarbageCollected<InvalidationSetMap>();
+}
+
+void InvalidationSetToSelectorMap::Trace(Visitor* visitor) const {
+  visitor->Trace(invalidation_set_map_);
+  visitor->Trace(current_selector_);
+}
+
+// static
+Persistent<InvalidationSetToSelectorMap>&
+InvalidationSetToSelectorMap::GetInstanceReference() {
+  DEFINE_STATIC_LOCAL(Persistent<InvalidationSetToSelectorMap>, instance, ());
+  return instance;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.h b/third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.h
new file mode 100644
index 0000000..2ff0ad9
--- /dev/null
+++ b/third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.h
@@ -0,0 +1,119 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_INVALIDATION_SET_TO_SELECTOR_MAP_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_INVALIDATION_SET_TO_SELECTOR_MAP_H_
+
+#include "third_party/blink/renderer/core/css/style_rule.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+
+namespace blink {
+
+class InvalidationSet;
+class StyleRule;
+
+// Implements a back-mapping from InvalidationSet entries to the selectors that
+// placed them there, for use in diagnostic traces.
+// Only active while the appropriate tracing configuration is enabled.
+class InvalidationSetToSelectorMap final
+    : public GarbageCollected<InvalidationSetToSelectorMap> {
+ public:
+  // A small helper to bundle together a StyleRule plus an index into its
+  // selector list.
+  class IndexedSelector final : public GarbageCollected<IndexedSelector> {
+   public:
+    IndexedSelector(StyleRule* style_rule, unsigned selector_index);
+    void Trace(Visitor*) const;
+    StyleRule* GetStyleRule() const;
+    unsigned GetSelectorIndex() const;
+    String GetSelectorText() const;
+
+   private:
+    Member<StyleRule> style_rule_;
+    unsigned selector_index_;
+  };
+  using IndexedSelectorList = HeapHashSet<Member<IndexedSelector>>;
+
+  enum class SelectorFeatureType {
+    kUnknown,
+    kClass,
+    kId,
+    kTagName,
+    kAttribute
+  };
+
+  // Instantiates a new mapping if a diagnostic tracing session with the
+  // appropriate configuration has started, or deletes an existing mapping if
+  // tracing is no longer enabled.
+  CORE_EXPORT static void StartOrStopTrackingIfNeeded();
+
+  // Call at the start and end of indexing features for a given selector.
+  static void BeginSelector(StyleRule* style_rule, unsigned selector_index);
+  static void EndSelector();
+
+  // Helper object for a Begin/EndSelector pair.
+  class SelectorScope {
+   public:
+    SelectorScope(StyleRule* style_rule, unsigned selector_index);
+    ~SelectorScope();
+  };
+
+  // Call for each feature recorded to an invalidation set.
+  static void RecordInvalidationSetEntry(
+      const InvalidationSet* invalidation_set,
+      SelectorFeatureType type,
+      const AtomicString& value);
+
+  // Call at the start and end of an invalidation set combine operation.
+  static void BeginInvalidationSetCombine(const InvalidationSet* target,
+                                          const InvalidationSet* source);
+  static void EndInvalidationSetCombine();
+
+  // Helper object for a Begin/EndInvalidationSetCombine pair.
+  class CombineScope {
+   public:
+    CombineScope(const InvalidationSet* target, const InvalidationSet* source);
+    ~CombineScope();
+  };
+
+  // Given an invalidation set and a selector feature representing an entry in
+  // that invalidation set, returns a list of selectors that contributed to that
+  // entry existing in that invalidation set.
+  static const IndexedSelectorList* Lookup(
+      const InvalidationSet* invalidation_set,
+      SelectorFeatureType type,
+      const AtomicString& value);
+
+  InvalidationSetToSelectorMap();
+  void Trace(Visitor*) const;
+
+ protected:
+  friend class InvalidationSetToSelectorMapTest;
+  CORE_EXPORT static Persistent<InvalidationSetToSelectorMap>&
+  GetInstanceReference();
+
+ private:
+  // The back-map is stored in two levels: first from an invalidation set
+  // pointer to a map of entries, then from each entry to a list of selectors.
+  // We don't retain a strong pointer to the InvalidationSet because we don't
+  // need it for any purpose other than as a lookup key.
+  using InvalidationSetEntry = std::pair<SelectorFeatureType, AtomicString>;
+  using InvalidationSetEntryMap =
+      HeapHashMap<InvalidationSetEntry, Member<IndexedSelectorList>>;
+  using InvalidationSetMap =
+      HeapHashMap<const InvalidationSet*, Member<InvalidationSetEntryMap>>;
+
+  Member<InvalidationSetMap> invalidation_set_map_;
+  Member<IndexedSelector> current_selector_;
+  unsigned combine_recursion_depth_ = 0;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_INVALIDATION_SET_TO_SELECTOR_MAP_H_
diff --git a/third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map_test.cc b/third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map_test.cc
new file mode 100644
index 0000000..2eb5e74
--- /dev/null
+++ b/third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map_test.cc
@@ -0,0 +1,201 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/inspector/invalidation_set_to_selector_map.h"
+
+#include "base/test/trace_event_analyzer.h"
+#include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+
+namespace blink {
+
+class InvalidationSetToSelectorMapTest : public PageTestBase {
+ protected:
+  void SetUp() override {
+    PageTestBase::SetUp();
+    CHECK(GetInstance() == nullptr);
+  }
+  void TearDown() override {
+    PageTestBase::TearDown();
+
+    // Ensure we do not carry over an instance from one test to another.
+    InvalidationSetToSelectorMap::StartOrStopTrackingIfNeeded();
+    CHECK(GetInstance() == nullptr);
+  }
+
+  void StartTracing() {
+    trace_analyzer::Start(
+        TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking"));
+  }
+  void StartTracingWithoutInvalidationTracking() {
+    trace_analyzer::Start(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"));
+  }
+  std::unique_ptr<trace_analyzer::TraceAnalyzer> StopTracing() {
+    return trace_analyzer::Stop();
+  }
+  InvalidationSetToSelectorMap* GetInstance() {
+    return InvalidationSetToSelectorMap::GetInstanceReference().Get();
+  }
+};
+
+TEST_F(InvalidationSetToSelectorMapTest, TrackerLifetime) {
+  ASSERT_EQ(GetInstance(), nullptr);
+
+  StartTracing();
+  SetBodyInnerHTML(R"HTML(<div id=d>D</div>)HTML");
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_NE(GetInstance(), nullptr);
+  GetElementById("d")->setAttribute(html_names::kStyleAttr,
+                                    AtomicString("color: red"));
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_NE(GetInstance(), nullptr);
+
+  StopTracing();
+  GetElementById("d")->setAttribute(html_names::kStyleAttr,
+                                    AtomicString("color: green"));
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(GetInstance(), nullptr);
+
+  StartTracingWithoutInvalidationTracking();
+  GetElementById("d")->setAttribute(html_names::kStyleAttr,
+                                    AtomicString("color: blue"));
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(GetInstance(), nullptr);
+  StopTracing();
+}
+
+TEST_F(InvalidationSetToSelectorMapTest, ClassMatch) {
+  StartTracing();
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .a .x { color: red; }
+      .b .x { color: green; }
+      .c .x { color: blue; }
+    </style>
+    <div id=parent class=a>Parent
+      <div class=x>Child</div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  GetElementById("parent")->setAttribute(html_names::kClassAttr,
+                                         AtomicString("b"));
+  UpdateAllLifecyclePhasesForTest();
+
+  auto analyzer = StopTracing();
+  trace_analyzer::TraceEventVector events;
+  analyzer->FindEvents(trace_analyzer::Query::EventNameIs(
+                           "StyleInvalidatorInvalidationTracking"),
+                       &events);
+  size_t found_event_count = 0;
+  for (auto event : events) {
+    ASSERT_TRUE(event->HasDictArg("data"));
+    base::Value::Dict data_dict = event->GetKnownArgAsDict("data");
+    std::string* reason = data_dict.FindString("reason");
+    if (reason != nullptr && *reason == "Invalidation set matched class") {
+      base::Value::List* selector_list = data_dict.FindList("selectors");
+      if (selector_list != nullptr) {
+        EXPECT_EQ(selector_list->size(), 1u);
+        EXPECT_EQ((*selector_list)[0], ".b .x");
+        found_event_count++;
+      }
+    }
+  }
+  EXPECT_EQ(found_event_count, 1u);
+}
+
+TEST_F(InvalidationSetToSelectorMapTest, ClassMatchWithMultipleInvalidations) {
+  StartTracing();
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .a .x { color: red; }
+      .b .x { color: green; }
+      .c .x { color: blue; }
+    </style>
+    <div id=parent class=a>Parent
+      <div class=x>Child</div>
+      <div class=x>Child</div>
+      <div class=x>Child</div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  GetElementById("parent")->setAttribute(html_names::kClassAttr,
+                                         AtomicString("b"));
+  UpdateAllLifecyclePhasesForTest();
+
+  auto analyzer = StopTracing();
+  trace_analyzer::TraceEventVector events;
+  analyzer->FindEvents(trace_analyzer::Query::EventNameIs(
+                           "StyleInvalidatorInvalidationTracking"),
+                       &events);
+  size_t found_event_count = 0;
+  for (auto event : events) {
+    ASSERT_TRUE(event->HasDictArg("data"));
+    base::Value::Dict data_dict = event->GetKnownArgAsDict("data");
+    std::string* reason = data_dict.FindString("reason");
+    if (reason != nullptr && *reason == "Invalidation set matched class") {
+      base::Value::List* selector_list = data_dict.FindList("selectors");
+      if (selector_list != nullptr) {
+        EXPECT_EQ(selector_list->size(), 1u);
+        EXPECT_EQ((*selector_list)[0], ".b .x");
+        found_event_count++;
+      }
+    }
+  }
+  EXPECT_EQ(found_event_count, 3u);
+}
+
+TEST_F(InvalidationSetToSelectorMapTest, ClassMatchWithCombine) {
+  StartTracing();
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      .a .x { color: red; }
+      .b .x { color: green; }
+      .c .x { color: blue; }
+    </style>
+    <style>
+      .b .w .x { color: black; }
+    </style>
+    <div id=parent class=a>Parent
+      <div class=x>Child</div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  GetElementById("parent")->setAttribute(html_names::kClassAttr,
+                                         AtomicString("b"));
+  UpdateAllLifecyclePhasesForTest();
+
+  auto analyzer = StopTracing();
+  trace_analyzer::TraceEventVector events;
+  analyzer->FindEvents(trace_analyzer::Query::EventNameIs(
+                           "StyleInvalidatorInvalidationTracking"),
+                       &events);
+  size_t found_event_count = 0;
+  for (auto event : events) {
+    ASSERT_TRUE(event->HasDictArg("data"));
+    base::Value::Dict data_dict = event->GetKnownArgAsDict("data");
+    std::string* reason = data_dict.FindString("reason");
+    if (reason != nullptr && *reason == "Invalidation set matched class") {
+      base::Value::List* selector_list = data_dict.FindList("selectors");
+      if (selector_list != nullptr) {
+        EXPECT_EQ(selector_list->size(), 2u);
+        // The map stores selectors in a HeapHashSet; they can be output to the
+        // trace event list in either order.
+        if ((*selector_list)[0] == ".b .x") {
+          EXPECT_EQ((*selector_list)[1], ".b .w .x");
+        } else {
+          EXPECT_EQ((*selector_list)[0], ".b .w .x");
+          EXPECT_EQ((*selector_list)[1], ".b .x");
+        }
+        found_event_count++;
+      }
+    }
+  }
+  EXPECT_EQ(found_event_count, 1u);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc b/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc
index 54c9198..f1f11f0 100644
--- a/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc
+++ b/third_party/blink/renderer/core/layout/anchor_evaluator_impl.cc
@@ -56,6 +56,33 @@
   }
 }
 
+// https://drafts.csswg.org/css-anchor-position-1/#valdef-anchor-inside
+// https://drafts.csswg.org/css-anchor-position-1/#valdef-anchor-outside
+CSSAnchorValue PhysicalAnchorValueFromInsideOutside(CSSAnchorValue anchor_value,
+                                                    bool is_y_axis,
+                                                    bool is_right_or_bottom) {
+  switch (anchor_value) {
+    case CSSAnchorValue::kInside: {
+      if (is_y_axis) {
+        return is_right_or_bottom ? CSSAnchorValue::kBottom
+                                  : CSSAnchorValue::kTop;
+      }
+      return is_right_or_bottom ? CSSAnchorValue::kRight
+                                : CSSAnchorValue::kLeft;
+    }
+    case CSSAnchorValue::kOutside: {
+      if (is_y_axis) {
+        return is_right_or_bottom ? CSSAnchorValue::kTop
+                                  : CSSAnchorValue::kBottom;
+      }
+      return is_right_or_bottom ? CSSAnchorValue::kLeft
+                                : CSSAnchorValue::kRight;
+    }
+    default:
+      return anchor_value;
+  }
+}
+
 }  // namespace
 
 PhysicalAnchorReference::PhysicalAnchorReference(
@@ -228,6 +255,8 @@
   anchor_value = PhysicalAnchorValueFromLogicalOrAuto(
       anchor_value, container_converter.GetWritingDirection(),
       self_writing_direction, is_y_axis);
+  anchor_value = PhysicalAnchorValueFromInsideOutside(anchor_value, is_y_axis,
+                                                      is_right_or_bottom);
   LayoutUnit value;
   switch (anchor_value) {
     case CSSAnchorValue::kCenter: {
@@ -286,6 +315,10 @@
       value += LayoutUnit::FromFloatRound(size * percentage / 100);
       break;
     }
+    case CSSAnchorValue::kInside:
+    case CSSAnchorValue::kOutside:
+      // Should have been handled by `PhysicalAnchorValueFromInsideOutside`.
+      [[fallthrough]];
     case CSSAnchorValue::kStart:
     case CSSAnchorValue::kEnd:
     case CSSAnchorValue::kSelfStart:
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 3b1e502..76481222 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -913,12 +913,6 @@
     diff.SetNeedsRecomputeVisualOverflow();
   }
 
-  bool has_clip = HasOutOfFlowPosition() && !HasAutoClip();
-  bool other_has_clip = other.HasOutOfFlowPosition() && !other.HasAutoClip();
-  if (has_clip != other_has_clip || (has_clip && Clip() != other.Clip())) {
-    diff.SetCSSClipChanged();
-  }
-
   if (DiffCompositingReasonsChanged(other, field_diff)) {
     diff.SetCompositingReasonsChanged();
   }
@@ -935,6 +929,13 @@
   if (field_diff & kBorderRadius) {
     diff.SetBorderRadiusChanged();
   }
+  if (field_diff & kClip) {
+    bool has_clip = HasOutOfFlowPosition() && !HasAutoClip();
+    bool other_has_clip = other.HasOutOfFlowPosition() && !other.HasAutoClip();
+    if (has_clip != other_has_clip || (has_clip && Clip() != other.Clip())) {
+      diff.SetCSSClipChanged();
+    }
+  }
   if (field_diff & kClipPath) {
     diff.SetClipPathChanged();
   }
@@ -950,12 +951,26 @@
   if (field_diff & kOpacity) {
     diff.SetOpacityChanged();
   }
+  if (field_diff & kScrollbarStyle) {
+    if (HasPseudoElementStyle(kPseudoIdScrollbar) !=
+            other.HasPseudoElementStyle(kPseudoIdScrollbar) ||
+        UsesStandardScrollbarStyle() != other.UsesStandardScrollbarStyle()) {
+      diff.SetNeedsFullLayout();
+      diff.SetNeedsNormalPaintInvalidation();
+    }
+  }
   if (field_diff & kTextDecoration) {
     diff.SetTextDecorationOrColorChanged();
   }
   if (field_diff & kTransformProperty) {
     diff.SetTransformPropertyChanged();
   }
+  if (field_diff & kVisibility) {
+    if ((Visibility() == EVisibility::kCollapse) !=
+        (other.Visibility() == EVisibility::kCollapse)) {
+      diff.SetNeedsFullLayout();
+    }
+  }
   if (field_diff & kZIndex) {
     diff.SetZIndexChanged();
   }
@@ -980,23 +995,7 @@
 bool ComputedStyle::DiffNeedsFullLayoutAndPaintInvalidation(
     const ComputedStyle& other,
     uint32_t field_diff) const {
-  // FIXME: Not all cases in this method need both full layout and paint
-  // invalidation.
-  // Should move cases into DiffNeedsFullLayout() if
-  // - don't need paint invalidation at all;
-  // - or the layoutObject knows how to exactly invalidate paints caused by the
-  //   layout change instead of forced full paint invalidation.
-
-  if (ComputedStyleBase::DiffNeedsFullLayoutAndPaintInvalidation(*this,
-                                                                 other)) {
-    return true;
-  }
-
   if (IsDisplayTableType(Display())) {
-    if (field_diff & kTable) {
-      return true;
-    }
-
     // In the collapsing border model, 'hidden' suppresses other borders, while
     // 'none' does not, so these style differences can be width differences.
     if ((BorderCollapse() == EBorderCollapse::kCollapse) &&
@@ -1018,15 +1017,6 @@
           other.BorderRightStyle() == EBorderStyle::kHidden))) {
       return true;
     }
-  } else if (IsDisplayListItem()) {
-    if (field_diff & kListItem) {
-      return true;
-    }
-  }
-
-  if ((Visibility() == EVisibility::kCollapse) !=
-      (other.Visibility() == EVisibility::kCollapse)) {
-    return true;
   }
 
   // Movement of non-static-positioned object is special cased in
@@ -1144,10 +1134,6 @@
     return true;
   }
 
-  if (field_diff & kBorderRadius) {
-    return true;
-  }
-
   if ((field_diff & kOutline) && !OutlineVisuallyEqual(other)) {
     return true;
   }
diff --git a/third_party/blink/renderer/core/style/computed_style_diff_functions.json5 b/third_party/blink/renderer/core/style/computed_style_diff_functions.json5
index 862a35ee..36b16ad 100644
--- a/third_party/blink/renderer/core/style/computed_style_diff_functions.json5
+++ b/third_party/blink/renderer/core/style/computed_style_diff_functions.json5
@@ -31,19 +31,6 @@
   },
   data: [
     {
-        name: "DiffNeedsFullLayoutAndPaintInvalidation",
-        methods_to_diff: [
-          {
-            method: "HasPseudoElementStyle(kPseudoIdScrollbar)",
-            field_dependencies: ["StyleType"]
-          },
-          {
-            method: "UsesStandardScrollbarStyle()",
-            field_dependencies: ["StyleType"]
-          },
-        ],
-    },
-    {
         name: "DiffNeedsPaintInvalidation",
         methods_to_diff: [
           {
diff --git a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5 b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
index 72988d6..d3b579c 100644
--- a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
+++ b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
@@ -245,6 +245,7 @@
       reset_on_new_style: true,
       custom_compare: true,
       computed_style_custom_functions: ["getter"],
+      invalidate: ["scrollbar-style"],
     },
     // Whether any non-universal highlight selectors were found when collecting
     // rules for the originating element. Stored in the *originating* style, and
@@ -358,6 +359,7 @@
       default_value: "true",
       field_group: "visual",
       computed_style_custom_functions: ["setter"],
+      invalidate: ["clip"],
     },
     {
       name: "HasAutoZIndex",
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index a95b9bf6..c823dab8 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -92,6 +92,8 @@
 
 namespace blink {
 
+using ::cc::UsePaintCache;
+
 BASE_FEATURE(kDisableCanvasOverdrawOptimization,
              "DisableCanvasOverdrawOptimization",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -1980,7 +1982,8 @@
     }
   } else {
     image = image_source->GetSourceImageForCanvas(
-        FlushReason::kDrawImage, &source_image_status, default_object_size);
+        FlushReason::kDrawImage, &source_image_status, default_object_size,
+        kPremultiplyAlpha);
     if (source_image_status == kUndecodableSourceImageStatus) {
       exception_state.ThrowDOMException(
           DOMExceptionCode::kInvalidStateError,
@@ -2181,7 +2184,8 @@
   gfx::SizeF default_object_size(Width(), Height());
   scoped_refptr<Image> image_for_rendering =
       image_source->GetSourceImageForCanvas(FlushReason::kCreatePattern,
-                                            &status, default_object_size);
+                                            &status, default_object_size,
+                                            kPremultiplyAlpha);
 
   switch (status) {
     case kNormalSourceImageStatus:
@@ -2310,7 +2314,7 @@
   SourceImageStatus source_image_status = kInvalidSourceImageStatus;
   scoped_refptr<Image> image = image_source->GetSourceImageForCanvas(
       FlushReason::kDrawMesh, &source_image_status,
-      gfx::SizeF(Width(), Height()));
+      gfx::SizeF(Width(), Height()), kPremultiplyAlpha);
   switch (source_image_status) {
     case kUndecodableSourceImageStatus:
       exception_state.ThrowDOMException(
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
index 3577f7e..a797e29b 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
@@ -56,7 +56,6 @@
 enum class V8CanvasStyleType;
 class GPUTexture;
 class V8UnionCanvasFilterOrString;
-using cc::UsePaintCache;
 
 class MODULES_EXPORT BaseRenderingContext2D : public CanvasPath {
  public:
@@ -715,7 +714,7 @@
   void DrawPathInternal(const CanvasPath&,
                         CanvasRenderingContext2DState::PaintType,
                         SkPathFillType,
-                        UsePaintCache);
+                        cc::UsePaintCache);
   void DrawImageInternal(cc::PaintCanvas*,
                          CanvasImageSource*,
                          Image*,
@@ -725,7 +724,7 @@
                          const cc::PaintFlags*);
   void ClipInternal(const Path&,
                     const String& winding_rule_string,
-                    UsePaintCache);
+                    cc::UsePaintCache);
 
   bool IsPointInPathInternal(const Path&,
                              const double x,
@@ -786,7 +785,7 @@
       CanvasResourceProvider& resource_provider);
 
   bool origin_tainted_by_content_ = false;
-  UsePaintCache path2d_use_paint_cache_;
+  cc::UsePaintCache path2d_use_paint_cache_;
   int num_readbacks_performed_ = 0;
   unsigned read_count_ = 0;
   base::HashingLRUCache<String, CachedColor> color_cache_{8};
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d_test.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d_test.cc
index a8edc36..b6693401 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d_test.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d_test.cc
@@ -68,6 +68,7 @@
 using ::cc::SaveOp;
 using ::cc::SetMatrixOp;
 using ::cc::TranslateOp;
+using ::cc::UsePaintCache;
 using ::testing::IsEmpty;
 using ::testing::Not;
 using ::testing::Pointee;
diff --git a/third_party/blink/renderer/modules/shapedetection/shape_detector.cc b/third_party/blink/renderer/modules/shapedetection/shape_detector.cc
index 903397a..7b58d22 100644
--- a/third_party/blink/renderer/modules/shapedetection/shape_detector.cc
+++ b/third_party/blink/renderer/modules/shapedetection/shape_detector.cc
@@ -92,7 +92,8 @@
 
   SourceImageStatus source_image_status = kInvalidSourceImageStatus;
   scoped_refptr<Image> image = canvas_image_source->GetSourceImageForCanvas(
-      FlushReason::kShapeDetector, &source_image_status, size);
+      FlushReason::kShapeDetector, &source_image_status, size,
+      kPremultiplyAlpha);
   if (!image || source_image_status != kNormalSourceImageStatus) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       "Invalid element or state.");
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.cc b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
index a082b48..4dfa612 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
@@ -767,7 +767,7 @@
 
   SourceImageStatus status = kInvalidSourceImageStatus;
   auto image = image_source->GetSourceImageForCanvas(
-      FlushReason::kCreateVideoFrame, &status, source_size);
+      FlushReason::kCreateVideoFrame, &status, source_size, kPremultiplyAlpha);
   if (!image || status != kNormalSourceImageStatus) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       "Invalid source state");
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.h b/third_party/blink/renderer/modules/webcodecs/video_frame.h
index 7992f63..e1c288d 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame.h
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame.h
@@ -128,7 +128,7 @@
       FlushReason,
       SourceImageStatus*,
       const gfx::SizeF&,
-      const AlphaDisposition alpha_disposition = kPremultiplyAlpha) override;
+      const AlphaDisposition alpha_disposition) override;
 
   gfx::SizeF ElementSize(const gfx::SizeF&,
                          const RespectImageOrientationEnum) const override;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index b8940bc..7a4b7b2c 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -6082,7 +6082,7 @@
   SourceImageStatus source_image_status = kInvalidSourceImageStatus;
   scoped_refptr<Image> image = context_host->GetSourceImageForCanvas(
       FlushReason::kWebGLTexImage, &source_image_status,
-      gfx::SizeF(*params.width, *params.height));
+      gfx::SizeF(*params.width, *params.height), kPremultiplyAlpha);
   if (source_image_status != kNormalSourceImageStatus)
     return;
 
diff --git a/third_party/blink/renderer/platform/image-decoders/BUILD.gn b/third_party/blink/renderer/platform/image-decoders/BUILD.gn
index 3f45cc71..63c87fe7 100644
--- a/third_party/blink/renderer/platform/image-decoders/BUILD.gn
+++ b/third_party/blink/renderer/platform/image-decoders/BUILD.gn
@@ -76,9 +76,12 @@
     sources += [
       "avif/avif_image_decoder.cc",
       "avif/avif_image_decoder.h",
+      "avif/crabbyavif_image_decoder.cc",
+      "avif/crabbyavif_image_decoder.h",
     ]
 
     deps += [
+      "//third_party/crabbyavif",
       "//third_party/libavif",
       "//third_party/libavifinfo",
     ]
@@ -120,7 +123,10 @@
   }
 
   if (enable_av1_decoder) {
-    sources += [ "avif/avif_image_decoder_test.cc" ]
+    sources += [
+      "avif/avif_image_decoder_test.cc",
+      "avif/crabbyavif_image_decoder_test.cc",
+    ]
   }
 }
 
@@ -135,6 +141,7 @@
     "//third_party/blink/renderer/platform/wtf",
   ]
   seed_corpuses = [ "//third_party/blink/web_tests/images/bmp-suite/good" ]
+
   # TODO: crbug.com/324586211 - raise the memory limit to avoid OOMs in the
   # fuzzer harness. This can be removed once the global memory limit is raised.
   libfuzzer_options = [ "rss_limit_mb=4096" ]
diff --git a/third_party/blink/renderer/platform/image-decoders/image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
index 7467b46..cc29007 100644
--- a/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
+++ b/third_party/blink/renderer/platform/image-decoders/image_decoder.cc
@@ -31,6 +31,7 @@
 #include "media/media_buildflags.h"
 #include "skia/ext/cicp.h"
 #include "third_party/blink/public/common/buildflags.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h"
 #include "third_party/blink/renderer/platform/image-decoders/exif_reader.h"
@@ -46,6 +47,7 @@
 
 #if BUILDFLAG(ENABLE_AV1_DECODER)
 #include "third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h"
+#include "third_party/blink/renderer/platform/image-decoders/avif/crabbyavif_image_decoder.h"
 #endif
 
 namespace blink {
@@ -188,7 +190,9 @@
     return "image/bmp";
   }
 #if BUILDFLAG(ENABLE_AV1_DECODER)
-  if (AVIFImageDecoder::MatchesAVIFSignature(fast_reader)) {
+  if (base::FeatureList::IsEnabled(blink::features::kCrabbyAvif)
+          ? CrabbyAVIFImageDecoder::MatchesAVIFSignature(fast_reader)
+          : AVIFImageDecoder::MatchesAVIFSignature(fast_reader)) {
     return "image/avif";
   }
 #endif
@@ -294,9 +298,15 @@
                                                 max_decoded_bytes);
 #if BUILDFLAG(ENABLE_AV1_DECODER)
   } else if (mime_type == "image/avif") {
-    decoder = std::make_unique<AVIFImageDecoder>(
-        alpha_option, high_bit_depth_decoding_option, color_behavior,
-        max_decoded_bytes, animation_option);
+    if (base::FeatureList::IsEnabled(blink::features::kCrabbyAvif)) {
+      decoder = std::make_unique<CrabbyAVIFImageDecoder>(
+          alpha_option, high_bit_depth_decoding_option, color_behavior,
+          max_decoded_bytes, animation_option);
+    } else {
+      decoder = std::make_unique<AVIFImageDecoder>(
+          alpha_option, high_bit_depth_decoding_option, color_behavior,
+          max_decoded_bytes, animation_option);
+    }
 #endif
   }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 8b7a2ef..7abeb8f 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -3524,7 +3524,7 @@
       // Maintain author-defined ::selection highlight colors, even if they
       // match the text color.
       name: "SelectionRespectsColors",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "SendBeaconThrowForBlobWithNonSimpleType",
@@ -4052,6 +4052,13 @@
       status: "experimental",
     },
     {
+      // Unblock queued blocking touchmove after the main thread handles the
+      // touchstart and the first touchmove and they didn't call
+      // preventDefault(), instead of waiting for the browser to send
+      // touchscrollstarted.
+      name: "UnblockTouchMoveEarlier",
+    },
+    {
       name: "UnclosedFormControlIsInvalid",
       status: "experimental",
     },
diff --git a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc
index 8b86eb00..35f0c3cd 100644
--- a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc
+++ b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc
@@ -18,6 +18,7 @@
 #include "third_party/blink/public/common/input/web_gesture_event.h"
 #include "third_party/blink/public/common/input/web_input_event_attribution.h"
 #include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
 
@@ -182,7 +183,11 @@
                     const ui::LatencyInfo& latency_info,
                     mojom::blink::DidOverscrollParamsPtr overscroll,
                     std::optional<cc::TouchAction> touch_action) {
-    // callback_ can be null in tests.
+    // callback_ is null if we have already run it, in cases
+    // 1. the event had been a blocking touchmove before it was unblocked;
+    // 2. the event is an non-blocking event, and its callback was called when
+    //    the event was queued, then a blocking event was coalesced into the
+    //    the event.
     if (callback_) {
       std::move(callback_).Run(ack_result, latency_info, std::move(overscroll),
                                touch_action);
@@ -191,10 +196,12 @@
     if (!blocking_coalesced_callbacks_.empty()) {
       ui::LatencyInfo coalesced_latency_info = latency_info;
       coalesced_latency_info.set_coalesced();
-      for (auto&& callback : blocking_coalesced_callbacks_) {
-        coalesced_latency_info.set_trace_id(callback.second);
-        std::move(callback.first)
-            .Run(ack_result, coalesced_latency_info, nullptr, std::nullopt);
+      for (auto& callback : blocking_coalesced_callbacks_) {
+        if (callback.first) {
+          coalesced_latency_info.set_trace_id(callback.second);
+          std::move(callback.first)
+              .Run(ack_result, coalesced_latency_info, nullptr, std::nullopt);
+        }
       }
     }
 
@@ -207,6 +214,29 @@
               ? WebInputEventResult::kHandledApplication
               : WebInputEventResult::kNotHandled);
     }
+
+    queue->UnblockQueuedBlockingTouchMovesIfNeeded(event_->Event(), ack_result);
+  }
+
+  struct CallbackInfo {
+    HandledEventCallback callback;
+    ui::LatencyInfo latency_info;
+  };
+  void TakeCallbacksInto(Vector<CallbackInfo>& callbacks) {
+    if (callback_) {
+      callbacks.emplace_back(std::move(callback_), event_->latency_info());
+    }
+    if (!blocking_coalesced_callbacks_.empty()) {
+      ui::LatencyInfo coalesced_latency_info = event_->latency_info();
+      coalesced_latency_info.set_coalesced();
+      for (auto& callback : blocking_coalesced_callbacks_) {
+        if (callback.first) {
+          coalesced_latency_info.set_trace_id(callback.second);
+          callbacks.emplace_back(std::move(callback.first),
+                                 coalesced_latency_info);
+        }
+      }
+    }
   }
 
   bool originally_cancelable() const { return originally_cancelable_; }
@@ -353,9 +383,9 @@
     originally_cancelable =
         touch_event->dispatch_type == WebInputEvent::DispatchType::kBlocking;
 
-    // Adjust the |dispatchType| on the event since the compositor
-    // determined all event listeners are passive.
     if (!is_blocking) {
+      // Adjust the `dispatch_type` on the event since the compositor
+      // determined all event listeners are passive.
       touch_event->dispatch_type =
           WebInputEvent::DispatchType::kListenersNonBlockingPassive;
     }
@@ -791,4 +821,75 @@
   needs_low_latency_until_pointer_up_ = true;
 }
 
+void MainThreadEventQueue::UnblockQueuedBlockingTouchMovesIfNeeded(
+    const WebInputEvent& dispatched_event,
+    mojom::blink::InputEventResultState ack_result) {
+  if (!RuntimeEnabledFeatures::UnblockTouchMoveEarlierEnabled()) {
+    return;
+  }
+  if (!WebInputEvent::IsTouchEventType(dispatched_event.GetType())) {
+    return;
+  }
+
+  bool should_unblock_queued_touch_moves = false;
+  {
+    auto& touch_event = static_cast<const WebTouchEvent&>(dispatched_event);
+    if (touch_event.touch_start_or_first_touch_move) {
+      bool is_not_consumed_blocking =
+          touch_event.dispatch_type == WebInputEvent::DispatchType::kBlocking &&
+          ack_result == mojom::blink::InputEventResultState::kNotConsumed;
+      if (touch_event.GetType() == WebInputEvent::Type::kTouchStart) {
+        blocking_touch_start_not_consumed_ = is_not_consumed_blocking;
+      } else {
+        // `event` is the first touch move.
+        CHECK_EQ(touch_event.GetType(), WebInputEvent::Type::kTouchMove);
+        should_unblock_queued_touch_moves =
+            blocking_touch_start_not_consumed_ && is_not_consumed_blocking;
+      }
+    }
+  }
+  if (!should_unblock_queued_touch_moves) {
+    return;
+  }
+
+  // Neither the touchstart nor the first touchmove was consumed. The browser
+  // process will make the remaining of the touch sequence non-blocking, but
+  // we need to unblock the already queued blocking touchmove events and run
+  // the callbacks (collected in a vector to avoid locking during callbacks).
+  Vector<QueuedWebInputEvent::CallbackInfo> callbacks;
+  {
+    base::AutoLock lock(shared_state_lock_);
+    for (size_t i = 0; i < shared_state_.events_.size(); ++i) {
+      MainThreadEventQueueTask* task = shared_state_.events_.at(i).get();
+      if (!task->IsWebInputEvent()) {
+        continue;
+      }
+      auto* queued_event = static_cast<QueuedWebInputEvent*>(task);
+      WebInputEvent* event =
+          queued_event->mutable_coalesced_event()->EventPointer();
+      if (event->GetType() == WebInputEvent::Type::kTouchStart ||
+          event->GetType() == WebInputEvent::Type::kTouchEnd) {
+        break;
+      }
+      if (event->GetType() != WebInputEvent::Type::kTouchMove) {
+        continue;
+      }
+
+      auto* touch_event = static_cast<WebTouchEvent*>(event);
+      if (!touch_event->touch_start_or_first_touch_move &&
+          touch_event->dispatch_type ==
+              WebInputEvent::DispatchType::kBlocking) {
+        touch_event->dispatch_type =
+            WebInputEvent::DispatchType::kEventNonBlocking;
+        queued_event->TakeCallbacksInto(callbacks);
+      }
+    }
+  }
+  for (auto& callback_info : callbacks) {
+    std::move(callback_info.callback)
+        .Run(mojom::blink::InputEventResultState::kNotConsumed,
+             callback_info.latency_info, nullptr, std::nullopt);
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h
index 6dfcd1b6..6e2a171 100644
--- a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h
+++ b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h
@@ -170,6 +170,10 @@
 
   void ClearRafFallbackTimerForTesting();
 
+  void UnblockQueuedBlockingTouchMovesIfNeeded(
+      const WebInputEvent& dispatched_event,
+      mojom::blink::InputEventResultState ack_result);
+
   friend class QueuedWebInputEvent;
   friend class MainThreadEventQueueTest;
   friend class MainThreadEventQueueInitializationTest;
@@ -177,6 +181,7 @@
   const bool allow_raf_aligned_input_;
   bool last_touch_start_forced_nonblocking_due_to_fling_ = false;
   bool has_pointerrawupdate_handlers_ = false;
+  bool blocking_touch_start_not_consumed_ = false;
 
   // These variables are read on the compositor thread but are
   // written on the main thread, so we use atomics to keep them
diff --git a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue_unittest.cc b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue_unittest.cc
index 9d52ea2..02fb0a7 100644
--- a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue_unittest.cc
@@ -2,14 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h"
+
 #include <stddef.h>
 
 #include <new>
 #include <tuple>
 #include <utility>
-#include <vector>
 
 #include "base/auto_reset.h"
+#include "base/containers/adapters.h"
 #include "base/functional/bind.h"
 #include "base/strings/string_util.h"
 #include "base/test/test_simple_task_runner.h"
@@ -22,7 +24,8 @@
 #include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
 #include "third_party/blink/public/platform/scheduler/test/web_mock_thread_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/test/fake_widget_scheduler.h"
-#include "third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 namespace {
@@ -82,6 +85,11 @@
 
   virtual blink::WebCoalescedInputEvent* taskAsEvent() = 0;
   virtual unsigned taskAsClosure() const = 0;
+  virtual void Print(std::ostream* os) const = 0;
+
+  friend void PrintTo(const HandledTask& task, std::ostream* os) {
+    task.Print(os);
+  }
 };
 
 class HandledEvent : public HandledTask {
@@ -96,6 +104,15 @@
     return 0;
   }
 
+  void Print(std::ostream* os) const override {
+    *os << "event_type: " << event_.Event().GetType();
+    if (WebInputEvent::IsTouchEventType(event_.Event().GetType())) {
+      auto& touch_event = static_cast<const WebTouchEvent&>(event_.Event());
+      *os << " touch_id: " << touch_event.unique_touch_event_id
+          << " dispatch_type: " << touch_event.dispatch_type;
+    }
+  }
+
  private:
   blink::WebCoalescedInputEvent event_;
 };
@@ -110,6 +127,7 @@
     return nullptr;
   }
   unsigned taskAsClosure() const override { return closure_id_; }
+  void Print(std::ostream* os) const override { NOTREACHED(); }
 
  private:
   unsigned closure_id_;
@@ -121,26 +139,55 @@
   kCalledAfterHandleEvent,
 };
 
+void PrintTo(CallbackReceivedState state, std::ostream* os) {
+  const char* kCallbackReceivedStateToString[] = {
+      "Pending", "CalledWhileHandlingEvent", "CalledAfterHandleEvent"};
+  *os << kCallbackReceivedStateToString[static_cast<int>(state)];
+}
+
 class ReceivedCallback {
  public:
   ReceivedCallback()
       : ReceivedCallback(CallbackReceivedState::kPending, false) {}
 
-  ReceivedCallback(CallbackReceivedState state, bool coalesced_latency)
-      : state_(state), coalesced_latency_(coalesced_latency) {}
+  ReceivedCallback(CallbackReceivedState state,
+                   bool coalesced_latency,
+                   wtf_size_t after_handled_tasks = kNotFound)
+      : state_(state),
+        coalesced_latency_(coalesced_latency),
+        after_handled_tasks_(after_handled_tasks) {}
   bool operator==(const ReceivedCallback& other) const {
     return state_ == other.state_ &&
-           coalesced_latency_ == other.coalesced_latency_;
+           coalesced_latency_ == other.coalesced_latency_ &&
+           // Tests not caring about after_handled_tasks_ can leave it as
+           // kNotFound to ignore it.
+           (after_handled_tasks_ == kNotFound ||
+            other.after_handled_tasks_ == kNotFound ||
+            after_handled_tasks_ == other.after_handled_tasks_);
+  }
+  friend void PrintTo(const ReceivedCallback& callback, std::ostream* os) {
+    PrintTo(callback.state_, os);
+    if (callback.coalesced_latency_) {
+      *os << " coalesced";
+    }
+    if (callback.after_handled_tasks_ != kNotFound) {
+      *os << " after_handled_tasks=" << callback.after_handled_tasks_;
+    }
   }
 
  private:
   CallbackReceivedState state_;
   bool coalesced_latency_;
+  // The number of handled tasks when the callback is run, for tests to check
+  // the order of event handling and callbacks.
+  wtf_size_t after_handled_tasks_;
 };
 
 class HandledEventCallbackTracker {
  public:
-  HandledEventCallbackTracker() : handling_event_(false) {
+  explicit HandledEventCallbackTracker(
+      const Vector<std::unique_ptr<HandledTask>>& handled_tasks)
+      : handling_event_(false), handled_tasks_(handled_tasks) {
     weak_this_ = weak_ptr_factory_.GetWeakPtr();
   }
 
@@ -152,7 +199,7 @@
     return callback;
   }
 
-  void DidHandleEvent(size_t index,
+  void DidHandleEvent(wtf_size_t index,
                       blink::mojom::InputEventResultState ack_result,
                       const ui::LatencyInfo& latency,
                       mojom::blink::DidOverscrollParamsPtr params,
@@ -160,21 +207,30 @@
     callbacks_received_[index] = ReceivedCallback(
         handling_event_ ? CallbackReceivedState::kCalledWhileHandlingEvent
                         : CallbackReceivedState::kCalledAfterHandleEvent,
-        latency.coalesced());
+        latency.coalesced(), handled_tasks_.size());
   }
 
-  const std::vector<ReceivedCallback>& GetReceivedCallbacks() const {
+  const Vector<ReceivedCallback>& GetReceivedCallbacks() const {
     return callbacks_received_;
   }
 
   bool handling_event_;
 
  private:
-  std::vector<ReceivedCallback> callbacks_received_;
+  Vector<ReceivedCallback> callbacks_received_;
+  const Vector<std::unique_ptr<HandledTask>>& handled_tasks_;
   base::WeakPtr<HandledEventCallbackTracker> weak_this_;
   base::WeakPtrFactory<HandledEventCallbackTracker> weak_ptr_factory_{this};
 };
 
+MATCHER_P3(IsHandledTouchEvent, event_type, touch_id, dispatch_type, "") {
+  CHECK(WebInputEvent::IsTouchEventType(event_type));
+  auto& event = static_cast<const WebTouchEvent&>(arg->taskAsEvent()->Event());
+  return event.GetType() == event_type &&
+         event.unique_touch_event_id == touch_id &&
+         event.dispatch_type == dispatch_type;
+}
+
 class MockWidgetScheduler : public scheduler::FakeWidgetScheduler {
  public:
   MockWidgetScheduler() = default;
@@ -184,12 +240,16 @@
 };
 
 class MainThreadEventQueueTest : public testing::Test,
-                                 public MainThreadEventQueueClient {
+                                 public testing::WithParamInterface<bool>,
+                                 public MainThreadEventQueueClient,
+                                 private ScopedUnblockTouchMoveEarlierForTest {
  public:
   MainThreadEventQueueTest()
-      : main_task_runner_(new base::TestSimpleTaskRunner()) {
+      : ScopedUnblockTouchMoveEarlierForTest(GetParam()),
+        main_task_runner_(new base::TestSimpleTaskRunner()) {
     widget_scheduler_ = base::MakeRefCounted<MockWidgetScheduler>();
-    handler_callback_ = std::make_unique<HandledEventCallbackTracker>();
+    handler_callback_ =
+        std::make_unique<HandledEventCallbackTracker>(handled_tasks_);
   }
 
   void SetUp() override {
@@ -198,7 +258,7 @@
     queue_->ClearRafFallbackTimerForTesting();
   }
 
-  void HandleEvent(WebInputEvent& event,
+  void HandleEvent(const WebInputEvent& event,
                    blink::mojom::InputEventResultState ack_result) {
     base::AutoReset<bool> in_handle_event(&handler_callback_->handling_event_,
                                           true);
@@ -255,12 +315,16 @@
   bool HandleInputEvent(const blink::WebCoalescedInputEvent& event,
                         std::unique_ptr<cc::EventMetrics> metrics,
                         HandledEventCallback callback) override {
+    if (will_handle_input_event_callback_) {
+      will_handle_input_event_callback_.Run(event);
+    }
+
     if (!handle_input_event_)
       return false;
     auto handled_event = std::make_unique<HandledEvent>(event);
     handled_tasks_.push_back(std::move(handled_event));
-    std::move(callback).Run(blink::mojom::InputEventResultState::kNotConsumed,
-                            event.latency_info(), nullptr, std::nullopt);
+    std::move(callback).Run(main_thread_ack_state_, event.latency_info(),
+                            nullptr, std::nullopt);
     return true;
   }
   void InputEventsDispatched(bool raf_aligned) override {
@@ -271,20 +335,24 @@
   }
   void SetNeedsMainFrame() override { needs_main_frame_ = true; }
 
-  std::vector<ReceivedCallback> GetAndResetCallbackResults() {
+  Vector<ReceivedCallback> GetAndResetCallbackResults() {
     std::unique_ptr<HandledEventCallbackTracker> callback =
-        std::make_unique<HandledEventCallbackTracker>();
+        std::make_unique<HandledEventCallbackTracker>(handled_tasks_);
     handler_callback_.swap(callback);
     return callback->GetReceivedCallbacks();
   }
 
   void set_handle_input_event(bool handle) { handle_input_event_ = handle; }
 
+  void set_main_thread_ack_state(blink::mojom::InputEventResultState state) {
+    main_thread_ack_state_ = state;
+  }
+
  protected:
   scoped_refptr<base::TestSimpleTaskRunner> main_task_runner_;
   scoped_refptr<MockWidgetScheduler> widget_scheduler_;
   scoped_refptr<MainThreadEventQueue> queue_;
-  std::vector<std::unique_ptr<HandledTask>> handled_tasks_;
+  Vector<std::unique_ptr<HandledTask>> handled_tasks_;
   std::unique_ptr<HandledEventCallbackTracker> handler_callback_;
 
   bool needs_main_frame_ = false;
@@ -292,10 +360,19 @@
   bool raf_aligned_events_dispatched_ = false;
   bool non_raf_aligned_events_dispatched_ = false;
   base::TimeTicks frame_time_;
+  blink::mojom::InputEventResultState main_thread_ack_state_ =
+      blink::mojom::InputEventResultState::kNotConsumed;
   unsigned closure_count_ = 0;
+
+  // This allows a test to simulate concurrent action in the compositor thread
+  // when the main thread is dispatching events in the queue.
+  base::RepeatingCallback<void(const blink::WebCoalescedInputEvent&)>
+      will_handle_input_event_callback_;
 };
 
-TEST_F(MainThreadEventQueueTest, ClientDoesntHandleInputEvent) {
+INSTANTIATE_TEST_SUITE_P(All, MainThreadEventQueueTest, ::testing::Bool());
+
+TEST_P(MainThreadEventQueueTest, ClientDoesntHandleInputEvent) {
   // Prevent MainThreadEventQueueClient::HandleInputEvent() from handling the
   // event, and have it return false. Then the MainThreadEventQueue should
   // call the handled callback.
@@ -317,7 +394,7 @@
   HandleEvent(event2, blink::mojom::InputEventResultState::kNotConsumed);
   RunPendingTasksWithSimulatedRaf();
 
-  std::vector<ReceivedCallback> received = GetAndResetCallbackResults();
+  Vector<ReceivedCallback> received = GetAndResetCallbackResults();
   // We didn't handle the event in the client method.
   EXPECT_EQ(handled_tasks_.size(), 0u);
   // There's 1 reply callback for our 1 event.
@@ -331,7 +408,7 @@
                   CallbackReceivedState::kCalledAfterHandleEvent, false)));
 }
 
-TEST_F(MainThreadEventQueueTest, NonBlockingWheel) {
+TEST_P(MainThreadEventQueueTest, NonBlockingWheel) {
   WebMouseWheelEvent kEvents[4] = {
       SyntheticWebMouseWheelEventBuilder::Build(
           10, 10, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel),
@@ -425,7 +502,7 @@
   }
 }
 
-TEST_F(MainThreadEventQueueTest, NonBlockingTouch) {
+TEST_P(MainThreadEventQueueTest, NonBlockingTouch) {
   EXPECT_CALL(*widget_scheduler_,
               DidHandleInputEventOnMainThread(testing::_, testing::_))
       .Times(0);
@@ -521,7 +598,7 @@
   }
 }
 
-TEST_F(MainThreadEventQueueTest, BlockingTouch) {
+TEST_P(MainThreadEventQueueTest, BlockingTouch) {
   SyntheticWebTouchEvent kEvents[4];
   kEvents[0].PressPoint(10, 10);
   kEvents[1].PressPoint(10, 10);
@@ -575,7 +652,7 @@
                   CallbackReceivedState::kCalledWhileHandlingEvent, false)));
 }
 
-TEST_F(MainThreadEventQueueTest, InterleavedEvents) {
+TEST_P(MainThreadEventQueueTest, InterleavedEvents) {
   WebMouseWheelEvent kWheelEvents[2] = {
       SyntheticWebMouseWheelEventBuilder::Build(
           10, 10, 0, 53, 0, ui::ScrollGranularity::kScrollByPixel),
@@ -640,7 +717,7 @@
   }
 }
 
-TEST_F(MainThreadEventQueueTest, RafAlignedMouseInput) {
+TEST_P(MainThreadEventQueueTest, RafAlignedMouseInput) {
   WebMouseEvent mouseDown = SyntheticWebMouseEventBuilder::Build(
       WebInputEvent::Type::kMouseDown, 10, 10, 0);
 
@@ -729,7 +806,7 @@
             handled_tasks_.at(4)->taskAsEvent()->Event().GetModifiers());
 }
 
-TEST_F(MainThreadEventQueueTest, RafAlignedTouchInput) {
+TEST_P(MainThreadEventQueueTest, RafAlignedTouchInput) {
   SyntheticWebTouchEvent kEvents[3];
   kEvents[0].PressPoint(10, 10);
   kEvents[1].PressPoint(10, 10);
@@ -806,7 +883,7 @@
                   CallbackReceivedState::kCalledAfterHandleEvent, false)));
 }
 
-TEST_F(MainThreadEventQueueTest, RafAlignedTouchInputCoalescedMoves) {
+TEST_P(MainThreadEventQueueTest, RafAlignedTouchInputCoalescedMoves) {
   SyntheticWebTouchEvent kEvents[2];
   kEvents[0].PressPoint(10, 10);
   kEvents[0].MovePoint(0, 50, 50);
@@ -876,7 +953,7 @@
                   CallbackReceivedState::kCalledWhileHandlingEvent, false)));
 }
 
-TEST_F(MainThreadEventQueueTest, RafAlignedTouchInputThrottlingMoves) {
+TEST_P(MainThreadEventQueueTest, RafAlignedTouchInputThrottlingMoves) {
   EXPECT_CALL(*widget_scheduler_,
               DidHandleInputEventOnMainThread(testing::_, testing::_))
       .Times(3);
@@ -922,7 +999,7 @@
   EXPECT_EQ(0u, event_queue().size());
 }
 
-TEST_F(MainThreadEventQueueTest, LowLatency) {
+TEST_P(MainThreadEventQueueTest, LowLatency) {
   SyntheticWebTouchEvent kEvents[2];
   kEvents[0].PressPoint(10, 10);
   kEvents[1].PressPoint(10, 10);
@@ -996,7 +1073,7 @@
   EXPECT_EQ(0u, event_queue().size());
 }
 
-TEST_F(MainThreadEventQueueTest, BlockingTouchesDuringFling) {
+TEST_P(MainThreadEventQueueTest, BlockingTouchesDuringFling) {
   SyntheticWebTouchEvent kEvents;
   kEvents.PressPoint(10, 10);
   kEvents.touch_start_or_first_touch_move = true;
@@ -1081,7 +1158,7 @@
   EXPECT_TRUE(Equal(kEvents, *last_touch_event));
 }
 
-TEST_F(MainThreadEventQueueTest, BlockingTouchesOutsideFling) {
+TEST_P(MainThreadEventQueueTest, BlockingTouchesOutsideFling) {
   SyntheticWebTouchEvent kEvents;
   kEvents.PressPoint(10, 10);
   kEvents.touch_start_or_first_touch_move = true;
@@ -1156,7 +1233,7 @@
   EXPECT_TRUE(Equal(kEvents, *last_touch_event));
 }
 
-TEST_F(MainThreadEventQueueTest, QueueingEventTimestampRecorded) {
+TEST_P(MainThreadEventQueueTest, QueueingEventTimestampRecorded) {
   WebMouseEvent kEvent = SyntheticWebMouseEventBuilder::Build(
       blink::WebInputEvent::Type::kMouseDown);
   // Set event timestamp to be in the past to simulate actual event
@@ -1179,30 +1256,7 @@
   EXPECT_LT(kHandledEvent->TimeStamp(), kHandledEvent->QueuedTimeStamp());
 }
 
-class MainThreadEventQueueInitializationTest
-    : public testing::Test,
-      public MainThreadEventQueueClient {
- public:
-  MainThreadEventQueueInitializationTest() = default;
-
-  bool HandleInputEvent(const blink::WebCoalescedInputEvent& event,
-                        std::unique_ptr<cc::EventMetrics> metrics,
-                        HandledEventCallback callback) override {
-    std::move(callback).Run(blink::mojom::InputEventResultState::kNotConsumed,
-                            event.latency_info(), nullptr, std::nullopt);
-    return true;
-  }
-
-  void InputEventsDispatched(bool raf_aligned) override {}
-  void SetNeedsMainFrame() override {}
-
- protected:
-  scoped_refptr<MainThreadEventQueue> queue_;
-  blink::scheduler::WebMockThreadScheduler widget_scheduler_;
-  scoped_refptr<base::TestSimpleTaskRunner> main_task_runner_;
-};
-
-TEST_F(MainThreadEventQueueTest, QueuingTwoClosures) {
+TEST_P(MainThreadEventQueueTest, QueuingTwoClosures) {
   EXPECT_FALSE(main_task_runner_->HasPendingTask());
   EXPECT_EQ(0u, event_queue().size());
 
@@ -1216,7 +1270,7 @@
   EXPECT_EQ(2u, handled_tasks_.at(1)->taskAsClosure());
 }
 
-TEST_F(MainThreadEventQueueTest, QueuingClosureWithRafEvent) {
+TEST_P(MainThreadEventQueueTest, QueuingClosureWithRafEvent) {
   SyntheticWebTouchEvent kEvents[2];
   kEvents[0].PressPoint(10, 10);
   kEvents[1].PressPoint(10, 10);
@@ -1265,7 +1319,7 @@
             handled_tasks_.at(3)->taskAsEvent()->Event().GetType());
 }
 
-TEST_F(MainThreadEventQueueTest, QueuingClosuresBetweenEvents) {
+TEST_P(MainThreadEventQueueTest, QueuingClosuresBetweenEvents) {
   SyntheticWebTouchEvent kEvents[2];
   kEvents[0].PressPoint(10, 10);
   kEvents[1].PressPoint(10, 10);
@@ -1300,7 +1354,7 @@
             handled_tasks_.at(3)->taskAsEvent()->Event().GetType());
 }
 
-TEST_F(MainThreadEventQueueTest, BlockingTouchMoveBecomesNonBlocking) {
+TEST_P(MainThreadEventQueueTest, BlockingTouchMoveBecomesNonBlocking) {
   SyntheticWebTouchEvent kEvents[2];
   kEvents[0].PressPoint(10, 10);
   kEvents[0].MovePoint(0, 20, 20);
@@ -1348,7 +1402,7 @@
                 .dispatch_type);
 }
 
-TEST_F(MainThreadEventQueueTest, BlockingTouchMoveWithTouchEnd) {
+TEST_P(MainThreadEventQueueTest, BlockingTouchMoveWithTouchEnd) {
   SyntheticWebTouchEvent kEvents[2];
   kEvents[0].PressPoint(10, 10);
   kEvents[0].MovePoint(0, 20, 20);
@@ -1388,7 +1442,212 @@
                 .dispatch_type);
 }
 
-TEST_F(MainThreadEventQueueTest, UnbufferedDispatchTouchEvent) {
+TEST_P(MainThreadEventQueueTest,
+       UnblockTouchMoveAfterTouchStartAndFirstTouchMoveNotConsumed) {
+  if (!RuntimeEnabledFeatures::UnblockTouchMoveEarlierEnabled()) {
+    return;
+  }
+
+  SyntheticWebTouchEvent touch_start;
+  touch_start.PressPoint(10, 10);
+  touch_start.touch_start_or_first_touch_move = true;
+  ASSERT_EQ(WebInputEvent::Type::kTouchStart, touch_start.GetType());
+  ASSERT_EQ(WebInputEvent::DispatchType::kBlocking, touch_start.dispatch_type);
+
+  SyntheticWebTouchEvent touch_moves[3];
+  for (auto& touch_move : touch_moves) {
+    touch_move.MovePoint(0, 20, 30);
+    ASSERT_EQ(WebInputEvent::Type::kTouchMove, touch_move.GetType());
+    ASSERT_EQ(WebInputEvent::DispatchType::kBlocking, touch_move.dispatch_type);
+  }
+  touch_moves[0].touch_start_or_first_touch_move = true;
+
+  struct WillHandleInputEventCallback {
+    void Run(const WebCoalescedInputEvent& event) {
+      test.set_main_thread_ack_state(
+          blink::mojom::InputEventResultState::kNotConsumed);
+      if (event.Event().GetType() == WebInputEvent::Type::kTouchStart &&
+          consume_touch_start) {
+        test.set_main_thread_ack_state(
+            blink::mojom::InputEventResultState::kConsumed);
+      }
+      auto touch_id = static_cast<const WebTouchEvent&>(event.Event())
+                          .unique_touch_event_id;
+      if (touch_id == touch_moves[0].unique_touch_event_id &&
+          consume_first_touch_move) {
+        test.set_main_thread_ack_state(
+            blink::mojom::InputEventResultState::kConsumed);
+      }
+      // Simulates two new blocking touchmove events enqueued while the first
+      // touchmove is being dispatched, respectively.
+      if (touch_id == touch_moves[0].unique_touch_event_id) {
+        test.HandleEvent(touch_moves[1],
+                         blink::mojom::InputEventResultState::kNotConsumed);
+        test.HandleEvent(touch_moves[2],
+                         blink::mojom::InputEventResultState::kNotConsumed);
+      }
+    }
+
+    MainThreadEventQueueTest& test;
+    const SyntheticWebTouchEvent* touch_moves;
+    bool consume_touch_start = false;
+    bool consume_first_touch_move = false;
+  };
+  WillHandleInputEventCallback will_handle_input_event_callback{*this,
+                                                                touch_moves};
+
+  will_handle_input_event_callback_ =
+      base::BindRepeating(&WillHandleInputEventCallback::Run,
+                          base::Unretained(&will_handle_input_event_callback));
+
+  EXPECT_FALSE(main_task_runner_->HasPendingTask());
+  EXPECT_EQ(0u, event_queue().size());
+  EXPECT_CALL(*widget_scheduler_,
+              DidHandleInputEventOnMainThread(testing::_, testing::_))
+      .Times(4);
+  HandleEvent(touch_start, blink::mojom::InputEventResultState::kNotConsumed);
+  HandleEvent(touch_moves[0],
+              blink::mojom::InputEventResultState::kNotConsumed);
+  EXPECT_EQ(2u, event_queue().size());
+  RunPendingTasksWithSimulatedRaf();
+  EXPECT_EQ(0u, event_queue().size());
+  EXPECT_FALSE(main_task_runner_->HasPendingTask());
+  EXPECT_FALSE(needs_main_frame_);
+  EXPECT_THAT(
+      GetAndResetCallbackResults(),
+      testing::ElementsAre(
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 1u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 2u),
+          // These callbacks were run just after handling the first touchmove.
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 2u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent, true,
+                           2u)));
+  EXPECT_THAT(
+      handled_tasks_,
+      ::testing::ElementsAre(
+          // touch_start should remain blocking.
+          IsHandledTouchEvent(WebInputEvent::Type::kTouchStart,
+                              touch_start.unique_touch_event_id,
+                              WebInputEvent::DispatchType::kBlocking),
+          // touch_moves[0] should remain blocking.
+          IsHandledTouchEvent(WebInputEvent::Type::kTouchMove,
+                              touch_moves[0].unique_touch_event_id,
+                              WebInputEvent::DispatchType::kBlocking),
+          // touch_moves[1] was unblocked while it was in the queue.
+          // touch_moves[2] was coalesced into touch_moves[1].
+          IsHandledTouchEvent(WebInputEvent::Type::kTouchMove,
+                              touch_moves[1].unique_touch_event_id,
+                              WebInputEvent::DispatchType::kEventNonBlocking)));
+
+  // Start another touch sequence, with the first touch_move consumed. This
+  // is not in a standalone test case to test the last unblocking status won't
+  // leak into this sequence.
+  handled_tasks_.clear();
+  will_handle_input_event_callback.consume_first_touch_move = true;
+  EXPECT_CALL(*widget_scheduler_,
+              DidHandleInputEventOnMainThread(testing::_, testing::_))
+      .Times(4);
+  HandleEvent(touch_start, blink::mojom::InputEventResultState::kNotConsumed);
+  HandleEvent(touch_moves[0],
+              blink::mojom::InputEventResultState::kNotConsumed);
+  RunPendingTasksWithSimulatedRaf();
+  EXPECT_THAT(
+      GetAndResetCallbackResults(),
+      testing::ElementsAre(
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 1u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 2u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 3u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent, true,
+                           3u)));
+  EXPECT_THAT(handled_tasks_,
+              ::testing::ElementsAre(
+                  IsHandledTouchEvent(WebInputEvent::Type::kTouchStart,
+                                      touch_start.unique_touch_event_id,
+                                      WebInputEvent::DispatchType::kBlocking),
+                  IsHandledTouchEvent(WebInputEvent::Type::kTouchMove,
+                                      touch_moves[0].unique_touch_event_id,
+                                      WebInputEvent::DispatchType::kBlocking),
+                  IsHandledTouchEvent(WebInputEvent::Type::kTouchMove,
+                                      touch_moves[1].unique_touch_event_id,
+                                      WebInputEvent::DispatchType::kBlocking)));
+
+  // Start another touch sequence, with the touch start consumed.
+  handled_tasks_.clear();
+  will_handle_input_event_callback.consume_touch_start = true;
+  will_handle_input_event_callback.consume_first_touch_move = false;
+  EXPECT_CALL(*widget_scheduler_,
+              DidHandleInputEventOnMainThread(testing::_, testing::_))
+      .Times(4);
+  HandleEvent(touch_start, blink::mojom::InputEventResultState::kNotConsumed);
+  HandleEvent(touch_moves[0],
+              blink::mojom::InputEventResultState::kNotConsumed);
+  RunPendingTasksWithSimulatedRaf();
+  EXPECT_THAT(
+      GetAndResetCallbackResults(),
+      testing::ElementsAre(
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 1u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 2u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 3u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent, true,
+                           3u)));
+  EXPECT_THAT(handled_tasks_,
+              ::testing::ElementsAre(
+                  IsHandledTouchEvent(WebInputEvent::Type::kTouchStart,
+                                      touch_start.unique_touch_event_id,
+                                      WebInputEvent::DispatchType::kBlocking),
+                  IsHandledTouchEvent(WebInputEvent::Type::kTouchMove,
+                                      touch_moves[0].unique_touch_event_id,
+                                      WebInputEvent::DispatchType::kBlocking),
+                  IsHandledTouchEvent(WebInputEvent::Type::kTouchMove,
+                                      touch_moves[1].unique_touch_event_id,
+                                      WebInputEvent::DispatchType::kBlocking)));
+
+  // Start another touch sequence, neither the touch start nor the first touch
+  // move are consumed, like the first touch sequence.
+  handled_tasks_.clear();
+  will_handle_input_event_callback.consume_touch_start = false;
+  EXPECT_CALL(*widget_scheduler_,
+              DidHandleInputEventOnMainThread(testing::_, testing::_))
+      .Times(4);
+  HandleEvent(touch_start, blink::mojom::InputEventResultState::kNotConsumed);
+  HandleEvent(touch_moves[0],
+              blink::mojom::InputEventResultState::kNotConsumed);
+  RunPendingTasksWithSimulatedRaf();
+  EXPECT_THAT(
+      GetAndResetCallbackResults(),
+      testing::ElementsAre(
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 1u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 2u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent,
+                           false, 2u),
+          ReceivedCallback(CallbackReceivedState::kCalledAfterHandleEvent, true,
+                           2u)));
+  EXPECT_THAT(
+      handled_tasks_,
+      ::testing::ElementsAre(
+          IsHandledTouchEvent(WebInputEvent::Type::kTouchStart,
+                              touch_start.unique_touch_event_id,
+                              WebInputEvent::DispatchType::kBlocking),
+          IsHandledTouchEvent(WebInputEvent::Type::kTouchMove,
+                              touch_moves[0].unique_touch_event_id,
+                              WebInputEvent::DispatchType::kBlocking),
+          IsHandledTouchEvent(WebInputEvent::Type::kTouchMove,
+                              touch_moves[1].unique_touch_event_id,
+                              WebInputEvent::DispatchType::kEventNonBlocking)));
+}
+
+TEST_P(MainThreadEventQueueTest, UnbufferedDispatchTouchEvent) {
   SyntheticWebTouchEvent kEvents[3];
   kEvents[0].PressPoint(10, 10);
   kEvents[1].PressPoint(10, 10);
@@ -1425,7 +1684,7 @@
   EXPECT_FALSE(needs_main_frame_);
 }
 
-TEST_F(MainThreadEventQueueTest, PointerEventsCoalescing) {
+TEST_P(MainThreadEventQueueTest, PointerEventsCoalescing) {
   queue_->HasPointerRawUpdateEventHandlers(true);
   WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
       WebInputEvent::Type::kMouseMove, 10, 10, 0);
@@ -1454,7 +1713,7 @@
   EXPECT_FALSE(needs_main_frame_);
 }
 
-TEST_F(MainThreadEventQueueTest, PointerRawUpdateEvents) {
+TEST_P(MainThreadEventQueueTest, PointerRawUpdateEvents) {
   WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
       WebInputEvent::Type::kMouseMove, 10, 10, 0);
 
@@ -1496,7 +1755,7 @@
   EXPECT_FALSE(needs_main_frame_);
 }
 
-TEST_F(MainThreadEventQueueTest, UnbufferedDispatchMouseEvent) {
+TEST_P(MainThreadEventQueueTest, UnbufferedDispatchMouseEvent) {
   WebMouseEvent mouse_down = SyntheticWebMouseEventBuilder::Build(
       WebInputEvent::Type::kMouseDown, 10, 10, 0);
   WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
@@ -1537,7 +1796,7 @@
 // are not coalesced with other events. During pointer lock,
 // kRelativeMotionEvent is sent to the Renderer only to update the new screen
 // position. Events of this kind shouldn't be dispatched or coalesced.
-TEST_F(MainThreadEventQueueTest, PointerEventsWithRelativeMotionCoalescing) {
+TEST_P(MainThreadEventQueueTest, PointerEventsWithRelativeMotionCoalescing) {
   WebMouseEvent mouse_move = SyntheticWebMouseEventBuilder::Build(
       WebInputEvent::Type::kMouseMove, 10, 10, 0);
 
@@ -1634,7 +1893,7 @@
 
 // Verifies that after rAF-aligned or non-rAF-aligned events are dispatched,
 // clients are notified that the dispatch is done.
-TEST_F(MainThreadEventQueueTest, InputEventsDispatchedNotified) {
+TEST_P(MainThreadEventQueueTest, InputEventsDispatchedNotified) {
   WebKeyboardEvent key_down(WebInputEvent::Type::kRawKeyDown, 0,
                             base::TimeTicks::Now());
   WebKeyboardEvent key_up(WebInputEvent::Type::kKeyUp, 0,
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py
index 2389f4f..bfd5a1a2 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor.py
@@ -341,6 +341,10 @@
     url: str
     screenshot: str
 
+    @staticmethod
+    def decode_image(screenshot: 'ReftestScreenshot') -> bytes:
+        return base64.b64decode(screenshot['screenshot'].strip())
+
 
 @dataclass
 class BrowserOutput:
@@ -862,51 +866,83 @@
                                  html_diff_content.encode())
 
     def _write_screenshots(self, test_name: str, artifacts: Artifacts,
-                           screenshots: List[ReftestScreenshot]):
+                           screenshot1: ReftestScreenshot,
+                           screenshot2: ReftestScreenshot):
         """Write actual, expected, and diff screenshots to disk, if possible.
 
         Arguments:
-            test_name: Web test name (a path).
+            test_name: Web test name (a URL whose path is relative to
+                `web_tests/`).
             artifacts: Artifact manager.
-            screenshots: Each element represents a screenshot of either the test
-                result or one of its references.
+            screenshot1: A screenshot of either the test page or one of its
+                references that failed comparison.
+            screenshot2: The screenshot compared against `screenshot1`.
+
+        The screenshots may be in either order and may represent any compatible
+        comparison (test against reference, or match ref against mismatch ref).
 
         Returns:
             The diff stats if the screenshots are different.
         """
-        # Remember the two images so we can diff them later.
-        _, test_url = self.port.get_suite_name_and_base_test(test_name)
-        test_url = self.path_finder.strip_wpt_path(test_url)
-        actual_image_bytes = b''
-        expected_image_bytes = b''
+        _, base_test = self.port.get_suite_name_and_base_test(test_name)
+        test_url = self.path_finder.strip_wpt_path(base_test)
 
-        for screenshot in screenshots:
-            if not isinstance(screenshot, dict):
-                # Skip the relation string, like '!=' or '=='.
-                continue
-            # The URL produced by wptrunner will have a leading "/", which we
-            # trim away for easier comparison to the WPT name below.
-            url = screenshot['url']
-            if url.startswith('/'):
-                url = url[1:]
-            image_bytes = base64.b64decode(screenshot['screenshot'].strip())
+        wpt_dir = self.port.wpt_dir(base_test)
+        assert wpt_dir, f'{base_test!r} is not a WPT'
+        manifest = self.port.wpt_manifest(wpt_dir)
+        # `test_url` is the globally mounted URL (i.e., its canonical ID),
+        # whereas `url_from_root`'s path part is relative to the test root
+        # (i.e., `external/wpt` or `wpt_internal`). These URLs happen to be
+        # identical for `external/wpt`, which wptserve mounts to `/`.
+        url_from_root = base_test[len(f'{wpt_dir}/'):]
+        relation_by_ref = {
+            url: relation
+            for relation, url in manifest.extract_reference_list(url_from_root)
+        }
+        assert set(relation_by_ref.values()) <= {'==', '!='}
 
-            screenshot_key = 'expected_image'
-            file_suffix = test_failures.FILENAME_SUFFIX_EXPECTED
-            if url == test_url:
-                screenshot_key = 'actual_image'
-                file_suffix = test_failures.FILENAME_SUFFIX_ACTUAL
-                actual_image_bytes = image_bytes
+        test_screenshot = match_screenshot = mismatch_screenshot = None
+        for screenshot in [screenshot1, screenshot2]:
+            # Compare URLs with a leading `/` to follow the convention
+            # wptrunner uses. There can only be up to one of each type of
+            # screenshot.
+            if screenshot['url'] == f'/{test_url}':
+                assert not test_screenshot
+                test_screenshot = screenshot
+            elif relation_by_ref[screenshot['url']] == '==':
+                assert not match_screenshot
+                match_screenshot = screenshot
             else:
-                expected_image_bytes = image_bytes
+                assert not mismatch_screenshot
+                mismatch_screenshot = screenshot
 
-            screenshot_subpath = self.port.output_filename(
-                test_name, file_suffix, '.png')
-            artifacts.CreateArtifact(screenshot_key, screenshot_subpath,
-                                     image_bytes)
+        # Because fuzzy rules allow matches without pixel-by-pixel equality,
+        # the screenshots in a mismatch failure may be slightly different, so we
+        # still extract both.
+        if mismatch_screenshot:
+            expected = mismatch_screenshot
+            # For tests with both match and mismatch references, the references
+            # may be compared if the match reference is found to be equivalent
+            # to the test page.
+            actual = test_screenshot or match_screenshot
+        else:
+            expected, actual = match_screenshot, test_screenshot
+
+        assert expected
+        expected_image = ReftestScreenshot.decode_image(expected)
+        expected_subpath = self.port.output_filename(
+            test_name, test_failures.FILENAME_SUFFIX_EXPECTED, '.png')
+        artifacts.CreateArtifact('expected_image', expected_subpath,
+                                 expected_image)
+
+        assert actual
+        actual_image = ReftestScreenshot.decode_image(actual)
+        actual_subpath = self.port.output_filename(
+            test_name, test_failures.FILENAME_SUFFIX_ACTUAL, '.png')
+        artifacts.CreateArtifact('actual_image', actual_subpath, actual_image)
 
         diff_bytes, stats, error = self.port.diff_image(
-            expected_image_bytes, actual_image_bytes)
+            expected_image, actual_image)
         if error:
             _log.error(
                 'Error creating diff image for %s '
@@ -942,8 +978,11 @@
                 self._write_text_results(result, artifacts)
             screenshots = (extra or {}).get('reftest_screenshots') or []
             if screenshots:
+                # Remove the relation operator `==` or `!=` between the
+                # screenshot objects.
+                screenshot1, _, screenshot2 = screenshots
                 image_diff_stats = self._write_screenshots(
-                    result.name, artifacts, screenshots)
+                    result.name, artifacts, screenshot1, screenshot2)
 
         if message:
             self._write_log(result.name, artifacts, 'crash_log',
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor_unittest.py b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor_unittest.py
index 93d6fc6..3b8a55c 100644
--- a/third_party/blink/tools/blinkpy/w3c/wpt_results_processor_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/wpt_results_processor_unittest.py
@@ -41,8 +41,16 @@
                     'reftest': {
                         'reftest.html': [
                             'c3f2fb6f436da59d43aeda0a7e8a018084557033',
-                            [None, [['reftest-ref.html', '==']], {}],
-                        ]
+                            [None, [['/reftest-ref.html', '==']], {}],
+                        ],
+                        'reftest-multiple.html': [
+                            'c3f2fb6f436da59d43aeda0a7e8a018084557033',
+                            [
+                                None,
+                                [['/reftest-ref.html', '=='],
+                                 ['/reftest-mismatch.html', '!=']], {}
+                            ],
+                        ],
                     },
                     'testharness': {
                         'test.html': [
@@ -75,7 +83,10 @@
                     'reftest': {
                         'reftest.html': [
                             'c3f2fb6f436da59d43aeda0a7e8a018084557033',
-                            [None, [['reftest-ref.html', '==']], {}],
+                            [
+                                None,
+                                [['/wpt_internal/reftest-ref.html', '==']], {}
+                            ],
                         ],
                     },
                     'testharness': {
@@ -90,6 +101,14 @@
                 },
             }))
         self.fs.write_text_file(
+            self.path_finder.path_from_web_tests('VirtualTestSuites'),
+            json.dumps([{
+                'prefix': 'fake-vts',
+                'platforms': ['Linux'],
+                'bases': ['external/wpt/reftest-multiple.html'],
+                'args': ['--enable-features=FakeFeature'],
+            }]))
+        self.fs.write_text_file(
             self.path_finder.path_from_blink_tools('blinkpy', 'web_tests',
                                                    'results.html'),
             'results-viewer-body')
@@ -699,7 +718,7 @@
                         'reftest_screenshots': [{
                             'url': '/reftest.html',
                             'screenshot': 'abcd',
-                        }, {
+                        }, '==', {
                             'url': '/reftest-ref.html',
                             'screenshot': 'bcde',
                         }],
@@ -725,6 +744,44 @@
                                  '> abcd',
                              ]))
 
+    def test_extract_screenshots_match_and_mismatch(self):
+        self._event(action='test_start',
+                    test='/reftest-multiple.html',
+                    subsuite='fake-vts')
+        self._event(action='test_end',
+                    test='/reftest-multiple.html',
+                    subsuite='fake-vts',
+                    status='FAIL',
+                    expected='PASS',
+                    extra={
+                        'reftest_screenshots': [{
+                            'url': '/reftest-ref.html',
+                            'screenshot': 'abcd',
+                        }, '!=', {
+                            'url': '/reftest-mismatch.html',
+                            'screenshot': 'abcd',
+                        }],
+                    })
+        self.assertEqual(
+            self.fs.read_binary_file(
+                self.fs.join('/mock-checkout', 'out', 'Default',
+                             'layout-test-results', 'virtual', 'fake-vts',
+                             'external', 'wpt',
+                             'reftest-multiple-actual.png')),
+            base64.b64decode('abcd'))
+        self.assertEqual(
+            self.fs.read_binary_file(
+                self.fs.join('/mock-checkout', 'out', 'Default',
+                             'layout-test-results', 'virtual', 'fake-vts',
+                             'external', 'wpt',
+                             'reftest-multiple-expected.png')),
+            base64.b64decode('abcd'))
+        self.assertFalse(
+            self.fs.exists(
+                self.fs.join('/mock-checkout', 'out', 'Default',
+                             'layout-test-results', 'virtual', 'fake-vts',
+                             'external', 'wpt', 'reftest-multiple-diff.png')))
+
     def test_extract_screenshots_for_wpt_internal(self):
         self._event(action='test_start', test='/wpt_internal/reftest.html')
         self._event(action='test_end',
@@ -735,8 +792,8 @@
                         'reftest_screenshots': [{
                             'url': '/wpt_internal/reftest.html',
                             'screenshot': 'abcd',
-                        }, {
-                            'url': 'wpt_internal/reftest-ref.html',
+                        }, '==', {
+                            'url': '/wpt_internal/reftest-ref.html',
                             'screenshot': 'bcde',
                         }],
                     })
@@ -990,31 +1047,28 @@
                                return_value=(..., diff_stats, ...)):
             for _ in range(2):
                 self._event(action='suite_start')
-                self._event(action='test_start', test='/test.html')
-                self._event(action='test_status',
-                            test='/test.html',
-                            status='FAIL',
-                            expected='PASS',
-                            subtest='subtest')
+                self._event(action='test_start', test='/reftest.html')
                 self._event(action='process_output',
                             process='101',
                             command='chromedriver --port=101',
                             data='[101:101:INFO] This is Chrome version 125')
                 self._event(action='test_end',
-                            test='/test.html',
-                            status='OK',
+                            test='/reftest.html',
+                            status='FAIL',
+                            expected='PASS',
                             extra={
                                 'reftest_screenshots': [{
-                                    'url': '/test.html',
+                                    'url': '/reftest-ref.html',
                                     'screenshot': 'abcd',
+                                }, '==', {
+                                    'url': '/reftest.html',
+                                    'screenshot': 'bcde',
                                 }],
                                 'browser_pid':
                                 101,
                             })
-                self._event(action='test_start', test='/reftest.html')
-                self._event(action='test_end',
-                            test='/reftest.html',
-                            status='PASS')
+                self._event(action='test_start', test='/test.html')
+                self._event(action='test_end', test='/test.html', status='OK')
                 self._event(action='suite_end')
         self.processor.process_results_json()
 
@@ -1023,13 +1077,13 @@
                 self.fs.join('/mock-checkout', 'out', 'Default',
                              'layout-test-results', 'full_results.json')))
         self.assertEqual(full_json['num_regressions'], 1)
-        unexpected_fail = full_json['tests']['external']['wpt']['test.html']
+        unexpected_fail = full_json['tests']['external']['wpt']['reftest.html']
         self.assertTrue(unexpected_fail['has_stderr'])
         self.assertEqual(unexpected_fail['artifacts']['stderr'], [
             self.fs.join('layout-test-results', 'external', 'wpt',
-                         'test-stderr.txt'),
+                         'reftest-stderr.txt'),
             self.fs.join('layout-test-results', 'retry_1', 'external', 'wpt',
-                         'test-stderr.txt'),
+                         'reftest-stderr.txt'),
         ])
         self.assertEqual(unexpected_fail['image_diff_stats'], diff_stats)
 
@@ -1044,8 +1098,9 @@
         failing_results = json.loads(failing_results_match['json'])
         self.assertIn('external', failing_results['tests'])
         self.assertIn('wpt', failing_results['tests']['external'])
-        self.assertIn('test.html', failing_results['tests']['external']['wpt'])
-        self.assertNotIn('reftest.html',
+        self.assertIn('reftest.html',
+                      failing_results['tests']['external']['wpt'])
+        self.assertNotIn('test.html',
                          failing_results['tests']['external']['wpt'])
         self.assertRegex(self.fs.read_text_file(path_to_failing_results),
                          'ADD_RESULTS\(.*\);$')
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index c06023f812..bb6a9449 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2529,6 +2529,7 @@
 crbug.com/626703 external/wpt/uievents/mouse/mouse_boundary_events_after_removing_last_over_element.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+external/wpt/notifications/instance.https.window.html [ Timeout ]
 crbug.com/340657191 [ Linux ] external/wpt/html/semantics/forms/the-select-element/stylable-select/native-popup-with-datalist.tentative.html [ Failure ]
 crbug.com/340167179 [ Win11-arm64 ] external/wpt/fetch/api/request/request-consume.any.serviceworker.html [ Failure Timeout ]
 crbug.com/340167179 [ Win11-arm64 ] external/wpt/fetch/api/request/request-consume.any.worker.html [ Failure Timeout ]
@@ -7138,7 +7139,7 @@
 [ Debug Mac14 ] http/tests/webfont/font-display-intervention.html [ Failure Pass ]
 [ Debug Mac14 ] media/controls/closed-captions-dynamic-update.html [ Failure Pass ]
 [ Debug Mac14 ] scrollbars/scrollbar-iframe-click-does-not-blur-content.html [ Failure Pass ]
-[ Debug Mac14 ] virtual/fedcm-multi-idp/external/wpt/credential-management/fedcm-multi-idp/fedcm-multi-idp-mediation-optional.https.html [ Failure Timeout Pass ]
+[ Debug Mac14 ] virtual/fedcm-multi-idp/external/wpt/credential-management/fedcm-multi-idp/fedcm-multi-idp-mediation-optional.https.html [ Failure Pass Timeout ]
 [ Debug Mac14 ] virtual/fenced-frame-mparch/external/wpt/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html [ Failure Pass ]
 [ Debug Mac14 ] virtual/fenced-frame-mparch/external/wpt/html/anonymous-iframe/embedding.tentative.https.window.html?13-last [ Failure Pass ]
 [ Debug Mac14 ] virtual/fenced-frame-mparch/external/wpt/html/anonymous-iframe/embedding.tentative.https.window.html?8-8 [ Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index e653efcd..096ae4e 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -143564,7 +143564,7 @@
        ]
       ],
       "text-box-trim-start-001.html": [
-       "17d4acdea054c76804dd05ad83b301015f7a8e32",
+       "35ac848aa6291db0508fd4df3f0bf2e7f2f43e07",
        [
         "css/css-inline/text-box-trim/text-box-trim-start-001.html?class=cap",
         [
@@ -143604,6 +143604,76 @@
          ]
         ],
         {}
+       ],
+       [
+        "css/css-inline/text-box-trim/text-box-trim-start-001.html?class=vlr,alphabetic",
+        [
+         [
+          "/css/css-inline/text-box-trim/text-box-trim-start-001-ref.html?class=vlr,alphabetic",
+          "=="
+         ]
+        ],
+        {}
+       ],
+       [
+        "css/css-inline/text-box-trim/text-box-trim-start-001.html?class=vlr,leading",
+        [
+         [
+          "/css/css-inline/text-box-trim/text-box-trim-start-001-ref.html?class=vlr,leading",
+          "=="
+         ]
+        ],
+        {}
+       ],
+       [
+        "css/css-inline/text-box-trim/text-box-trim-start-001.html?class=vlr,text",
+        [
+         [
+          "/css/css-inline/text-box-trim/text-box-trim-start-001-ref.html?class=vlr,text",
+          "=="
+         ]
+        ],
+        {}
+       ],
+       [
+        "css/css-inline/text-box-trim/text-box-trim-start-001.html?class=vrl,cap",
+        [
+         [
+          "/css/css-inline/text-box-trim/text-box-trim-start-001-ref.html?class=vrl,cap",
+          "=="
+         ]
+        ],
+        {}
+       ],
+       [
+        "css/css-inline/text-box-trim/text-box-trim-start-001.html?class=vrl,ex",
+        [
+         [
+          "/css/css-inline/text-box-trim/text-box-trim-start-001-ref.html?class=vrl,ex",
+          "=="
+         ]
+        ],
+        {}
+       ],
+       [
+        "css/css-inline/text-box-trim/text-box-trim-start-001.html?class=vrl,leading",
+        [
+         [
+          "/css/css-inline/text-box-trim/text-box-trim-start-001-ref.html?class=vrl,leading",
+          "=="
+         ]
+        ],
+        {}
+       ],
+       [
+        "css/css-inline/text-box-trim/text-box-trim-start-001.html?class=vrl,text",
+        [
+         [
+          "/css/css-inline/text-box-trim/text-box-trim-start-001-ref.html?class=vrl,text",
+          "=="
+         ]
+        ],
+        {}
        ]
       ]
      }
@@ -275733,7 +275803,7 @@
        ]
       ],
       "popover-anchor-display.tentative.html": [
-       "a713540094a03ab52b5fd315551554581b362913",
+       "3dbd6c9a24dd3d420e8e943a8e1e679618180fb5",
        [
         null,
         [
@@ -296875,11 +296945,11 @@
       []
      ],
      "digital-identity-helper.js": [
-      "2020d6cda7271be351da9a9849014899bd68df01",
+      "8fff82745172154972dcda4c3c6cb98d4a5af5e1",
       []
      ],
      "digital-identity-iframe.html": [
-      "8e193ff09f90fb09b8e676849a0d9dbdb0f043b1",
+      "8b3a424d1e21d381b76849deb261762aec016298",
       []
      ],
      "echoing-nester.html": [
@@ -324158,7 +324228,7 @@
        []
       ],
       "text-box-trim-start-001-ref.html": [
-       "ac5f7d776302c61923f0edcb281c6f72dbd52b0c",
+       "40067cc592504c4622ded772b545a3e3a29e57dd",
        []
       ]
      }
@@ -349044,7 +349114,7 @@
      []
     ],
     "echo-policy-nested.html.headers": [
-     "419a3c3dd16dcf7d6dfdcbcd86ad26314f9ac368",
+     "ab319fc4ceb1d583a04832d665efb36bc87628a2",
      []
     ],
     "echo-policy.py": [
@@ -349253,11 +349323,11 @@
     },
     "required-policy": {
      "document-policy.html.headers": [
-      "20629ac15f53b45818e4357a33f544b7f523e466",
+      "ab319fc4ceb1d583a04832d665efb36bc87628a2",
       []
      ],
      "required-document-policy.html.headers": [
-      "ac1bf268b50a75411e6819abbadc8e493dd9a612",
+      "230ffe282a107537195993cf252f683ad1c97f52",
       []
      ],
      "separate-document-policies.html.headers": [
@@ -371933,7 +372003,7 @@
         ],
         "resources": {
          "stylable-select-styles.css": [
-          "bb613823e79750438277857eebe9ef9d0bdb839b",
+          "7f4c8b6c448af5ac01bdf85c906ecd8bc18bff79",
           []
          ],
          "stylable-select-utils.js": [
@@ -382746,7 +382816,15 @@
      []
     ],
     "getnotifications-sw.js": [
-     "331913b99358922f7d9743a8dc49218bc93004c2",
+     "ad2c11c6d5fca8492bc7f52bceaa15011d602037",
+     []
+    ],
+    "instance-checks.js": [
+     "31ff0b870937d5452607f4b99ae72d7db7610fe2",
+     []
+    ],
+    "instance-sw.js": [
+     "f08b17e15f8407ea7bef6ce47a040af9fd8db3bb",
      []
     ],
     "lang.https-expected.txt": [
@@ -382759,11 +382837,11 @@
     ],
     "resources": {
      "custom-data.js": [
-      "b21d28a1bb390575f34206c87aae1f7fd8655fd7",
+      "49a5c60d811338cc898bc64b1cbe8b3e41c54ca8",
       []
      ],
      "helpers.js": [
-      "ca44e32f7f4921011226b9647bc4ba5d76495980",
+      "6b418be03ed2d7c0697163b2758667172201289b",
       []
      ],
      "icon.png": [
@@ -386260,7 +386338,7 @@
      []
     ],
     "document-reporting-bypass-report-to.https.sub.html.sub.headers": [
-     "b2a3d20f482151a27a545c8894805737a57ff035",
+     "bbdd968c04f79d7fa1d324283f19b764b8e8cd4f",
      []
     ],
     "document-reporting-default-endpoint.https.sub.html.sub.headers": [
@@ -386268,11 +386346,11 @@
      []
     ],
     "document-reporting-named-endpoints.https.sub.html.sub.headers": [
-     "2d5a308db27f7b2595114bf7e4fd8fa9612dfc0e",
+     "89b7b1afec5165368db7cf724516dfa02bf4c052",
      []
     ],
     "document-reporting-override-endpoint.https.sub.html.sub.headers": [
-     "46954f4d5cbc9a17a9bcbf91481e905748441edd",
+     "f9c9bf45c44b49f58072f1558141c6ba583ca0bf",
      []
     ],
     "document-reporting-path-absolute.https.sub.html.sub.headers": [
@@ -399600,7 +399678,7 @@
        []
       ],
       "gather.json": [
-       "a01654637f14e29d3cdb69609b583e2f223cb3d7",
+       "acd7ad8775e6df514547ed5871d386b42b82c78a",
        []
       ],
       "gemm.json": [
@@ -440662,7 +440740,7 @@
      ]
     ],
     "digital-identity.https.html": [
-     "8ae9caa002d061862098b30341147601da6d10e0",
+     "cc279735bd7b5320a3eeb530144bd7b1a9e44c69",
      [
       null,
       {
@@ -443826,7 +443904,7 @@
       ]
      ],
      "inset-area-basic.html": [
-      "b89d0e2428965172ae769c7d1ac5a5ecb17c0170",
+      "3ede9dcd92584b4d50a5def737826f416a209c87",
       [
        null,
        {}
@@ -443882,7 +443960,7 @@
       ]
      ],
      "inset-area-with-insets.html": [
-      "2482b443130ec9a11a4d92e08d5c180126434c4f",
+      "f6a4cd3665224e1d71d78773256f655b0e9309e8",
       [
        null,
        {}
@@ -443954,7 +444032,7 @@
       ]
      ],
      "position-anchor-basics.html": [
-      "f9fe9dd6f8f1ad73076536ca00e8ea5ddc37df25",
+      "8039903c39a571480fc9117331dfa01b0ee6354a",
       [
        null,
        {}
@@ -444108,7 +444186,7 @@
       ]
      ],
      "property-interpolations.html": [
-      "954e5642dd7499474d147447699c07b50b5a4d1d",
+      "ddfad852f8917a662ed1b3936f577f4f3e91acf2",
       [
        null,
        {}
@@ -452791,14 +452869,14 @@
        ]
       ],
       "font-palette-values-invalid.html": [
-       "b93a48fb37df58702d50f05fb9f745749ddb3a25",
+       "a3a0a88ba68c8fcc1f8b00054d7b12bc08d4b7af",
        [
         null,
         {}
        ]
       ],
       "font-palette-values-valid.html": [
-       "99fceff2344ccead12a0af0c1ab5450070311e56",
+       "328e9a7dbf116f11a372ef72bd436c19b917d23c",
        [
         null,
         {}
@@ -483287,28 +483365,28 @@
     },
     "required-policy": {
      "document-policy.html": [
-      "aaa8d6920018efd0b3871cc46f201887fdaeec37",
+      "4beaf3f2164aaa75c05f9d2dfa2e2ace811e7ecf",
       [
        null,
        {}
       ]
      ],
      "no-document-policy.html": [
-      "8a3624440f3358381e3e51f9850dfc9285c4487f",
+      "00a721e8150db5d43f9d8c9b610c6c3aea84f42b",
       [
        null,
        {}
       ]
      ],
      "required-document-policy-nested.html": [
-      "33de2533a25517943a4ad7c2594d8fe03d1df485",
+      "0adba51c1999a283943df4f5756f2881255556c8",
       [
        null,
        {}
       ]
      ],
      "required-document-policy.html": [
-      "1058e3582abd836958ecc9d075fc18f76a77f95c",
+      "f710e6c8f2a28e678a5aa4346004e6cecf83faf5",
       [
        null,
        {}
@@ -582754,6 +582832,15 @@
           }
          ]
         ],
+        "select-accessibility-minimum-target-size.tentative.html": [
+         "364efd1554219493a5b9262c18a12317b9571037",
+         [
+          null,
+          {
+           "testdriver": true
+          }
+         ]
+        ],
         "select-datalist-options-idl.tentative.html": [
          "92eabdc5d8d341b6d4c16af3bb8b60700869daf8",
          [
@@ -609908,11 +609995,38 @@
       }
      ]
     ],
-    "instance.https.html": [
-     "5ccc6cd1e3a8ea01161469fefdba562ab8eea84d",
+    "instance.https.window.js": [
+     "ba7c103048fef2111875aafb8c82eb763073c661",
      [
-      null,
-      {}
+      "notifications/instance.https.window.html",
+      {
+       "script_metadata": [
+        [
+         "script",
+         "/resources/testdriver.js"
+        ],
+        [
+         "script",
+         "/resources/testdriver-vendor.js"
+        ],
+        [
+         "script",
+         "/service-workers/service-worker/resources/test-helpers.sub.js"
+        ],
+        [
+         "script",
+         "resources/helpers.js"
+        ],
+        [
+         "script",
+         "resources/custom-data.js"
+        ],
+        [
+         "script",
+         "instance-checks.js"
+        ]
+       ]
+      }
      ]
     ],
     "lang.https.html": [
@@ -614195,7 +614309,7 @@
      ]
     ],
     "pointerevent_after_target_appended.html": [
-     "712670d6479c819732825f8dad3609f9872001fe",
+     "a5cb82088e85a548159a8c0ab7700700fa10b4cd",
      [
       "pointerevents/pointerevent_after_target_appended.html?mouse",
       {
@@ -614237,7 +614351,7 @@
      ]
     ],
     "pointerevent_after_target_removed.html": [
-     "b63e8b92d18d11ac979a8da2b5753bc8126d3b05",
+     "97a1a83fc874a780f63651d1d52589d2d0dbe22e",
      [
       "pointerevents/pointerevent_after_target_removed.html?mouse",
       {
@@ -614422,6 +614536,15 @@
       }
      ]
     ],
+    "pointerevent_capture_mouse_and_release_and_capture_again.html": [
+     "d060338116c415b4aaf88aec07c5443b87e205da",
+     [
+      null,
+      {
+       "testdriver": true
+      }
+     ]
+    ],
     "pointerevent_capture_suppressing_mouse.html": [
      "0d7756335d1d9e7a5baf0a2f213963124d9850ef",
      [
@@ -614431,6 +614554,15 @@
       }
      ]
     ],
+    "pointerevent_capture_touch_and_release_at_got_capture.html": [
+     "095b67010d45db909e318207abe2bc1ca92f976f",
+     [
+      null,
+      {
+       "testdriver": true
+      }
+     ]
+    ],
     "pointerevent_change-touch-action-onpointerdown_touch.html": [
      "40dbbed72f437172bab73fde62ee487b6150b10d",
      [
@@ -628639,7 +628771,7 @@
      ]
     ],
     "document-reporting-bypass-report-to.https.sub.html": [
-     "394bc9e40a0a8b3dba5c2f176230d1e73dc0c46b",
+     "f599ef8511b9931fa1961cec33954254daec8fe8",
      [
       null,
       {}
@@ -628662,7 +628794,7 @@
      ]
     ],
     "document-reporting-named-endpoints.https.sub.html": [
-     "c24601147a2a2aac5dee708c9ec7a2f73594dde7",
+     "3e659872e05bf55d2efdeca3a6528a28f1191689",
      [
       null,
       {}
@@ -628676,7 +628808,7 @@
      ]
     ],
     "document-reporting-override-endpoint.https.sub.html": [
-     "9264786093e7a9f524727ad98a0c13b61e133bd2",
+     "e10f82a5e36a53b79833bde627db9cc4bc2755f2",
      [
       null,
       {}
@@ -634492,7 +634624,7 @@
    "service-workers": {
     "cache-storage": {
      "cache-abort.https.any.js": [
-      "960d1bb1bffd7dfa6ec8a35b6428baff231352e4",
+      "99f29b0a08bae82f4be0c0dee98ce5b31a941a48",
       [
        "service-workers/cache-storage/cache-abort.https.any.html",
        {
@@ -637851,7 +637983,7 @@
    },
    "shadow-dom": {
     "Document-caretPositionFromPoint.tentative.html": [
-     "2b97546d2e257ce0bf2cd5275058df01e40f6eed",
+     "f3053ee71150932451729aeab07dc1eb2c7920a1",
      [
       null,
       {}
@@ -638028,6 +638160,13 @@
        {}
       ]
      ],
+     "declarative-shadow-dom-write-to-iframe.html": [
+      "ab0af5878de4d957337fb8284950571310a1bb19",
+      [
+       null,
+       {}
+      ]
+     ],
      "declarative-with-disabled-shadow.html": [
       "bcf53403addb673b609ecbc2a4299d84a701fc3e",
       [
@@ -682396,7 +682535,7 @@
       ]
      ],
      "gather.https.any.js": [
-      "184e8033e6624cfd433494d7c01f917195c5b65d",
+      "96082437289e87c4a98cd8c5336a2c15c33a0bc0",
       [
        "webnn/validation_tests/gather.https.any.html",
        {
@@ -723713,7 +723852,14 @@
           ]
          ],
          "set_permission.py": [
-          "18f8e6fed000fe2cc6ae04adfa0cc965b89b5edc",
+          "574f2149841dd69e579bebca6efaaf25b007c255",
+          [
+           null,
+           {}
+          ]
+         ],
+         "user_context.py": [
+          "b45ddb12b7e59093210dfdf55d3d9008918b0b41",
           [
            null,
            {}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-inside-outside.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-inside-outside.html
new file mode 100644
index 0000000..aefd586
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-inside-outside.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>CSS Anchor Positioning: anchor(inside/outside)</title>
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#valdef-anchor-inside">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#valdef-anchor-outside">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<style>
+  #cb {
+    position: relative;
+    width: 400px;
+    height: 400px;
+    border: 1px solid black;
+  }
+  #anchor {
+    position: absolute;
+    top: 250px;
+    left: 150px;
+    background-color: skyblue;
+    width: 50px;
+    height: 50px;
+    anchor-name: --a;
+  }
+  .target {
+    position: absolute;
+    position-anchor: --a;
+    background-color: tomato;
+    width: 10px;
+    height: 10px;
+  }
+</style>
+<div id=cb>
+  <div id=anchor></div>
+  <div class=target style="left:anchor(inside)" data-offset-x=150></div>
+  <div class=target style="left:anchor(outside)" data-offset-x=200></div>
+  <div class=target style="right:anchor(inside)" data-offset-x=190></div>
+  <div class=target style="right:anchor(outside)" data-offset-x=140></div>
+  <div class=target style="top:anchor(inside)" data-offset-y=250></div>
+  <div class=target style="top:anchor(outside)" data-offset-y=300></div>
+  <div class=target style="bottom:anchor(inside)" data-offset-y=290></div>
+  <div class=target style="bottom:anchor(outside)" data-offset-y=240></div>
+  <!-- Logical -->
+  <div class=target style="inset-inline-start:anchor(inside)" data-offset-x=150></div>
+  <div class=target style="inset-inline-start:anchor(outside)" data-offset-x=200></div>
+  <div class=target style="inset-inline-end:anchor(inside)" data-offset-x=190></div>
+  <div class=target style="inset-inline-end:anchor(outside)" data-offset-x=140></div>
+  <div class=target style="inset-block-start:anchor(inside)" data-offset-y=250></div>
+  <div class=target style="inset-block-start:anchor(outside)" data-offset-y=300></div>
+  <div class=target style="inset-block-end:anchor(inside)" data-offset-y=290></div>
+  <div class=target style="inset-block-end:anchor(outside)" data-offset-y=240></div>
+</div>
+<script>
+  addEventListener('load', () => checkLayout('.target'));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-parse-valid.html b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-parse-valid.html
index 4690775..f3069649 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-parse-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-anchor-position/anchor-parse-valid.html
@@ -24,6 +24,8 @@
 ];
 
 const anchorSides = [
+  'inside',
+  'outside',
   'left',
   'right',
   'top',
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-invalid.html
index b93a48fb..a3a0a88 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-invalid.html
@@ -135,13 +135,18 @@
 @font-palette-values --A {
     override-colors: 0 light-dark(white, currentcolor);
 }
+
+/* 23 */
+@font-palette-values --A {
+    override-colors: 0 color-mix(in lch, red, color-mix(in lch, currentcolor, black));
+}
 </style>
 </head>
 <body>
 <script>
 let rules = document.getElementById("style").sheet.cssRules;
 test(function() {
-    assert_equals(rules.length, 23);
+    assert_equals(rules.length, 24);
 });
 
 test(function() {
@@ -331,6 +336,13 @@
     assert_equals(text.indexOf("override-colors"), -1);
     assert_equals(rule.overrideColors, "");
 });
+
+test(function() {
+    let text = rules[23].cssText;
+    let rule = rules[23];
+    assert_equals(text.indexOf("override-colors"), -1);
+    assert_equals(rule.overrideColors, "");
+});
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-valid.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-valid.html
index 99fceff2..328e9a7d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-valid.html
@@ -108,6 +108,11 @@
 @font-palette-values --Q {
     override-colors: 0 color-mix(in lch, red, blue);
 }
+
+/* 18 */
+@font-palette-values --R {
+    override-colors: 0 color-mix(in lch, color-mix(in lch, red, blue), color-mix(in lch, green, white));
+}
 </style>
 </head>
 <body>
@@ -398,6 +403,14 @@
     assert_equals(rule.basePalette, "");
     assert_equals(rule.overrideColors, "0 color-mix(in lch, red, blue)");
 });
+
+test(function() {
+    let rule = rules[18];
+    assert_equals(rule.name, "--R");
+    assert_equals(rule.fontFamily, "");
+    assert_equals(rule.basePalette, "");
+    assert_equals(rule.overrideColors, "0 color-mix(in lch, color-mix(in lch, red, blue), color-mix(in lch, green, white))");
+});
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/notifications/getnotifications-sw.js b/third_party/blink/web_tests/external/wpt/notifications/getnotifications-sw.js
index 331913b9..ad2c11c 100644
--- a/third_party/blink/web_tests/external/wpt/notifications/getnotifications-sw.js
+++ b/third_party/blink/web_tests/external/wpt/notifications/getnotifications-sw.js
@@ -1,4 +1,5 @@
 importScripts("/resources/testharness.js");
+importScripts("resources/helpers.js");
 
 async function cleanup() {
   for (const n of await registration.getNotifications()) {
@@ -29,14 +30,9 @@
   }
 }
 
-async function untilActivate() {
-  if (registration.active) {
-    return;
-  }
-  return new Promise(resolve => {
-    addEventListener("activate", resolve, { once: true });
-  });
-}
+promise_setup(async () => {
+  await untilActivate();
+});
 
 promise_test(async t => {
   await new Promise((resolve, reject) => {
@@ -45,7 +41,7 @@
         resolve();
       }
     });
-    untilActivate().then(() => postAll("notification-create")).catch(reject);
+    postAll("notification-create").catch(reject);
   });
   await test_notification(t, "Created from window");
 }, "Get notification created from window");
diff --git a/third_party/blink/web_tests/external/wpt/notifications/instance-checks.js b/third_party/blink/web_tests/external/wpt/notifications/instance-checks.js
new file mode 100644
index 0000000..31ff0b87
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/notifications/instance-checks.js
@@ -0,0 +1,40 @@
+const notification_args = [
+  "Radio check",
+  {
+      dir: "ltr",
+      lang: "aa",
+      body: "This is a radio check.",
+      tag: "radio_check999",
+      icon: `${location.origin}/icon.png`,
+      data: fakeCustomData,
+  }
+];
+
+// promise_tests because we need to wait for promise_setup
+function notification_instance_test(createFn, testTitle) {
+  let n;
+  promise_test(async t => {
+    n = await createFn(t);
+  }, `${testTitle}: Setup`);
+  promise_test(async () => {
+    assert_equals("Radio check", n.title)
+  }, `${testTitle}: Attribute exists with expected value: title`)
+  promise_test(async () => {
+    assert_equals("ltr", n.dir)
+  }, `${testTitle}: Attribute exists with expected value: dir`)
+  promise_test(async () => {
+    assert_equals("aa", n.lang)
+  }, `${testTitle}: Attribute exists with expected value: lang`)
+  promise_test(async () => {
+    assert_equals("This is a radio check.", n.body)
+  }, `${testTitle}: Attribute exists with expected value: body`)
+  promise_test(async () => {
+    assert_equals("radio_check999", n.tag)
+  }, `${testTitle}: Attribute exists with expected value: tag`)
+  promise_test(async () => {
+    assert_equals(`${location.origin}/icon.png`, n.icon)
+  }, `${testTitle}: Attribute exists with expected value: icon`)
+  promise_test(async () => {
+    assert_custom_data(n.data);
+  }, `${testTitle}: Attribute exists with expected value: data`)
+}
diff --git a/third_party/blink/web_tests/external/wpt/notifications/instance-sw.js b/third_party/blink/web_tests/external/wpt/notifications/instance-sw.js
new file mode 100644
index 0000000..f08b17e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/notifications/instance-sw.js
@@ -0,0 +1,34 @@
+importScripts("/resources/testharness.js");
+importScripts("resources/helpers.js");
+importScripts("resources/custom-data.js");
+importScripts("instance-checks.js");
+
+promise_setup(async () => {
+  await untilActivate();
+});
+
+notification_instance_test(async t => {
+  t.add_cleanup(closeAllNotifications);
+
+  await registration.showNotification(...notification_args);
+
+  let notifications = await registration.getNotifications();
+  assert_equals(notifications.length, 1, "The list should include one notification");
+
+  return notifications[0];
+}, "getNotifications()");
+
+// Doing this separately because this times out on Blink and GeckoView
+notification_instance_test(async t => {
+  t.add_cleanup(closeAllNotifications);
+
+  await registration.showNotification(...notification_args);
+
+  let notifications = await registration.getNotifications();
+  assert_equals(notifications.length, 1, "The list should include one notification");
+
+  notifications[0].close();
+  const ev = await new Promise(resolve => addEventListener("notificationclose", resolve, { once: true }));
+
+  return ev.notification;
+}, "notificationclose");
diff --git a/third_party/blink/web_tests/external/wpt/notifications/instance.https.html b/third_party/blink/web_tests/external/wpt/notifications/instance.https.html
deleted file mode 100644
index 5ccc6cd..0000000
--- a/third_party/blink/web_tests/external/wpt/notifications/instance.https.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>Notification instance basic tests</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/custom-data.js"></script>
-<script>
-var n = new Notification("Radio check",
-    {
-        dir: "ltr",
-        lang: "aa",
-        body: "This is a radio check.",
-        tag: "radio_check999",
-        icon: "http://example.com/icon.png",
-        data: fakeCustomData,
-    }
-)
-n.onshow = function() {
-    n.close()
-}
-test(function() {
-    assert_true(n instanceof Notification)
-},"Notification instance exists.")
-test(function() {
-    assert_true("close" in n)
-},"Attribute exists: close")
-test(function() {
-    assert_true("onclick" in n)
-},"Attribute exists: onclick")
-test(function() {
-    assert_true("onshow" in n)
-},"Attribute exists: onshow")
-test(function() {
-    assert_true("onerror" in n)
-},"Attribute exists: onerror")
-test(function() {
-    assert_true("onclose" in n)
-},"Attribute exists: onclose")
-test(function() {
-    assert_equals("Radio check", n.title)
-},"Attribute exists with expected value: title")
-test(function() {
-    assert_equals("ltr", n.dir)
-},"Attribute exists with expected value: dir")
-test(function() {
-    assert_equals("aa", n.lang)
-},"Attribute exists with expected value: lang")
-test(function() {
-    assert_equals("This is a radio check.", n.body)
-},"Attribute exists with expected value: body")
-test(function() {
-    assert_equals("radio_check999", n.tag)
-},"Attribute exists with expected value: tag")
-test(function() {
-    assert_equals("http://example.com/icon.png", n.icon)
-},"Attribute exists with expected value: icon")
-test(function() {
-    assert_custom_data(n.data);
-},"Attribute exists with expected value: data")
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/notifications/instance.https.window.js b/third_party/blink/web_tests/external/wpt/notifications/instance.https.window.js
new file mode 100644
index 0000000..ba7c103
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/notifications/instance.https.window.js
@@ -0,0 +1,18 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+// META: script=resources/helpers.js
+// META: script=resources/custom-data.js
+// META: script=instance-checks.js
+
+promise_setup(async () => {
+  await trySettingPermission("granted");
+});
+
+notification_instance_test(() => {
+  const n = new Notification(...notification_args);
+  n.close();
+  return n;
+}, "new Notification()");
+
+service_worker_test("instance-sw.js", "Service worker test setup");
diff --git a/third_party/blink/web_tests/external/wpt/notifications/resources/custom-data.js b/third_party/blink/web_tests/external/wpt/notifications/resources/custom-data.js
index b21d28a..49a5c60d 100644
--- a/third_party/blink/web_tests/external/wpt/notifications/resources/custom-data.js
+++ b/third_party/blink/web_tests/external/wpt/notifications/resources/custom-data.js
@@ -1,8 +1,7 @@
 var fakeCustomData = (function() {
   var buffer = new ArrayBuffer(2);
   new DataView(buffer).setInt16(0, 42, true);
-  var canvas = document.createElement("canvas");
-  canvas.width = canvas.height = 100;
+  var canvas = new OffscreenCanvas(100, 100);
   var context = canvas.getContext("2d");
 
   var map = new Map();
diff --git a/third_party/blink/web_tests/external/wpt/notifications/resources/helpers.js b/third_party/blink/web_tests/external/wpt/notifications/resources/helpers.js
index ca44e32..6b418be 100644
--- a/third_party/blink/web_tests/external/wpt/notifications/resources/helpers.js
+++ b/third_party/blink/web_tests/external/wpt/notifications/resources/helpers.js
@@ -33,3 +33,13 @@
     throw new Error(`Should have the permission ${perm} to continue`);
   }
 }
+
+// Use this in service workers where activation is required e.g. when testing showNotification()
+async function untilActivate() {
+  if (registration.active) {
+    return;
+  }
+  return new Promise(resolve => {
+    addEventListener("activate", resolve, { once: true });
+  });
+}
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_after_target_appended.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_after_target_appended.html
index 712670d6..a5cb820 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_after_target_appended.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_after_target_appended.html
@@ -158,15 +158,29 @@
 
   setup();
 
+  const hoverable = pointer_type != "touch";
+
   // Tests for dispatched pointer events.
-  addPromiseTestForNewChild("pointerdown", "pointer", [
-    "pointerover@parent", "pointerenter@parent",
-    "pointerdown@parent", "(child-attached)",
-    "pointerout@parent", "pointerover@child", "pointerenter@child",
-    "pointerup@child",
-    "pointerdown@child", "pointerup@child",
-    "pointerout@child", "pointerleave@child", "pointerleave@parent"
-  ]);
+  addPromiseTestForNewChild(
+    "pointerdown",
+    "pointer",
+    hoverable
+      ? ["pointerover@parent", "pointerenter@parent",
+        "pointerdown@parent", "(child-attached)",
+        "pointerout@parent", "pointerover@child", "pointerenter@child",
+        "pointerup@child",
+        "pointerdown@child", "pointerup@child",
+        "pointerout@child", "pointerleave@child", "pointerleave@parent"]
+      : ["pointerover@parent", "pointerenter@parent",
+        "pointerdown@parent", "(child-attached)",
+        // pointerup should imply a pointermove over the attached child.
+        "pointerout@parent", "pointerover@child", "pointerenter@child",
+        // pointerup should cause pointerout/pointerleave if the input source is not hoverable.
+        "pointerup@child", "pointerout@child", "pointerleave@child", "pointerleave@parent",
+        // then, pointerdown should imply a pointermove again.
+        "pointerover@child", "pointerenter@child", "pointerenter@parent", "pointerdown@child",
+        "pointerup@child", "pointerout@child", "pointerleave@child", "pointerleave@parent"]
+  );
   addPromiseTestForNewChild("pointerup", "pointer", [
     "pointerover@parent", "pointerenter@parent",
     "pointerdown@parent", "pointerup@parent", "(child-attached)",
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_after_target_removed.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_after_target_removed.html
index b63e8b9..97a1a83 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_after_target_removed.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_after_target_removed.html
@@ -104,19 +104,43 @@
 
   setup();
 
+  const hoverable = pointer_type != "touch";
+
   // Tests for dispatched pointer events.
-  addPromiseTest("pointerdown", "pointer", [
-    "pointerover@child", "pointerenter@parent", "pointerenter@child",
-    "pointerdown@child", "(child-removed)", "pointerover@parent", "pointerup@parent",
-    "pointerdown@parent", "pointerup@parent",
-    "pointerout@parent", "pointerleave@parent"
-  ]);
-  addPromiseTest("pointerup", "pointer", [
-    "pointerover@child", "pointerenter@parent", "pointerenter@child",
-    "pointerdown@child", "pointerup@child", "(child-removed)",
-    "pointerover@parent", "pointerdown@parent", "pointerup@parent",
-    "pointerout@parent", "pointerleave@parent"
-  ]);
+  addPromiseTest(
+    "pointerdown",
+    "pointer",
+    hoverable
+      ? ["pointerover@child", "pointerenter@parent", "pointerenter@child",
+        "pointerdown@child", "(child-removed)", "pointerover@parent", "pointerup@parent",
+        "pointerdown@parent", "pointerup@parent",
+        "pointerout@parent", "pointerleave@parent"]
+      : ["pointerover@child", "pointerenter@parent", "pointerenter@child", "pointerdown@child",
+        "(child-removed)", "pointerover@parent",
+        // pointerup should cause pointerout/pointerleave if the input source is not hoverable.
+        "pointerup@parent", "pointerout@parent", "pointerleave@parent",
+        // then, pointerdown should imply a pointermove again.
+        "pointerover@parent", "pointerenter@parent", "pointerdown@parent",
+        "pointerup@parent", "pointerout@parent", "pointerleave@parent"]
+  );
+  addPromiseTest(
+    "pointerup",
+    "pointer",
+    hoverable
+      ? ["pointerover@child", "pointerenter@parent", "pointerenter@child",
+        "pointerdown@child", "pointerup@child", "(child-removed)",
+        "pointerover@parent", "pointerdown@parent", "pointerup@parent",
+        "pointerout@parent", "pointerleave@parent"]
+      : ["pointerover@child", "pointerenter@parent", "pointerenter@child",
+        "pointerdown@child", "pointerup@child", "(child-removed)",
+        // only pointerleave should be fired if the input source is not hoverable
+        // because pointerup removed the pointerout event target which is not
+        // received pointerover event, but the pointer becomes invalid.
+        "pointerleave@parent",
+        // then, pointerdown should imply a pointermove again.
+        "pointerover@parent", "pointerenter@parent", "pointerdown@parent", "pointerup@parent",
+        "pointerout@parent", "pointerleave@parent"]
+  );
 
   // Same tests for dispatched compatibility mouse events.
   addPromiseTest("mousedown", "mouse", [
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_capture_mouse_and_release_and_capture_again.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_capture_mouse_and_release_and_capture_again.html
new file mode 100644
index 0000000..d060338
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_capture_mouse_and_release_and_capture_again.html
@@ -0,0 +1,90 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Testing pointer events for mouse when capturing the pointer with different element from the pointerdown target and
+release it at got capture and capture it again at lost the first capture</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+"use strict";
+
+addEventListener("load", () => {
+  promise_test(async () => {
+    const listener = document.getElementById("listener");
+    const target = document.getElementById("target");
+    let events = [];
+    function logEvent(event) {
+      events.push({
+        type: event.type,
+        target: event.target,
+        pointerType: event.pointerType,
+      });
+    }
+    function stringifyEvents(events) {
+      if (!events.length) {
+        return "[]";
+      }
+      let result = "";
+      for (const event of events) {
+        if (result == "") {
+          result = "[";
+        } else {
+          result += ", ";
+        }
+        result += `${event.type}@${
+            event.target.id ? event.target.id : event.target.localName
+          }(pointerType=${event.pointerType})`;
+      }
+      result += "]";
+      return result;
+    }
+    target.addEventListener("pointerdown", event => {
+      logEvent(event);
+      listener.setPointerCapture(event.pointerId);
+    });
+    listener.addEventListener("gotpointercapture", event => {
+      logEvent(event);
+      listener.releasePointerCapture(event.pointerId);
+    });
+    listener.addEventListener("lostpointercapture", event => {
+      logEvent(event);
+      listener.setPointerCapture(event.pointerId);
+    });
+    listener.addEventListener("pointerover", logEvent);
+    listener.addEventListener("pointermove", logEvent);
+    listener.addEventListener("pointerup", logEvent);
+    listener.addEventListener("pointerout", logEvent);
+
+    await new test_driver.Actions()
+      .pointerMove(0, 0, {origin: target})
+      .pointerDown()
+      .pointerMove(1, 1, {origin: target})
+      .pointerUp()
+      .send();
+
+    const expectedEvents = [
+      {type: "pointerdown", target: target, pointerType: "mouse"},
+      {type: "pointerover", target: listener, pointerType: "mouse"},
+      {type: "gotpointercapture", target: listener, pointerType: "mouse"},
+      {type: "pointermove", target: listener, pointerType: "mouse"},
+      {type: "lostpointercapture", target: listener, pointerType: "mouse"},
+      // The pointer is over the target, not over the listener.  Therefore,
+      // when the listener loses the capture, pointerout should be fired on
+      // the listener since it won't receive pointer events until the pointer
+      // moves over the listener.
+      {type: "pointerout", target: listener, pointerType: "mouse"},
+    ];
+    assert_equals(stringifyEvents(events), stringifyEvents(expectedEvents));
+  }, "A pointerout should be fired on the capturing element after losing the capture");
+}, {once: true});
+</script>
+</head>
+<body>
+  <div id="listener">div id=listener</div>
+  <div id="target">div id=target</div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_capture_touch_and_release_at_got_capture.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_capture_touch_and_release_at_got_capture.html
new file mode 100644
index 0000000..095b670
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_capture_touch_and_release_at_got_capture.html
@@ -0,0 +1,88 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Testing pointer events for touch when capturing the pointer with different element from the pointerdown target and release it at got pointer capture</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+"use strict";
+
+addEventListener("load", () => {
+  promise_test(async () => {
+    const listener = document.getElementById("listener");
+    const target = document.getElementById("target");
+    let events = [];
+    function logEvent(event) {
+      events.push({
+        type: event.type,
+        target: event.target,
+        pointerType: event.pointerType,
+        isPrimary: event.isPrimary,
+      });
+    }
+    function stringifyEvents(events) {
+      if (!events.length) {
+        return "[]";
+      }
+      let result = "";
+      for (const event of events) {
+        if (result == "") {
+          result = "[";
+        } else {
+          result += ", ";
+        }
+        result += `${event.type}@${
+            event.target.id ? event.target.id : event.target.localName
+          }(pointerType=${event.pointerType},isPrimary=${event.isPrimary})`;
+      }
+      result += "]";
+      return result;
+    }
+    target.addEventListener("pointerdown", event => {
+      logEvent(event);
+      listener.setPointerCapture(event.pointerId);
+    });
+    listener.addEventListener("gotpointercapture", event => {
+      logEvent(event);
+      listener.releasePointerCapture(event.pointerId);
+    });
+    listener.addEventListener("lostpointercapture", logEvent);
+    listener.addEventListener("pointerover", logEvent);
+    listener.addEventListener("pointermove", logEvent);
+    listener.addEventListener("pointerup", logEvent);
+    listener.addEventListener("pointerout", logEvent);
+
+    await new test_driver.Actions()
+      .addPointer("touch_pointer", "touch")
+      .pointerMove(0, 0, {origin: target})
+      .pointerDown()
+      .pointerMove(1, 1, {origin: target})
+      .pointerUp()
+      .send();
+
+    const expectedEvents = [
+      {type: "pointerdown", target: target, pointerType: "touch", isPrimary: true},
+      {type: "pointerover", target: listener, pointerType: "touch", isPrimary: true},
+      {type: "gotpointercapture", target: listener, pointerType: "touch", isPrimary: true},
+      {type: "pointermove", target: listener, pointerType: "touch", isPrimary: true},
+      {type: "lostpointercapture", target: listener, pointerType: "touch", isPrimary: true},
+      // The pointer is over the target, not over the listener.  Therefore,
+      // when the listener loses the capture, pointerout should be fired on
+      // the listener since it won't receive pointer events until the pointer
+      // moves over the listener.
+      {type: "pointerout", target: listener, pointerType: "touch", isPrimary: true},
+    ];
+    assert_equals(stringifyEvents(events), stringifyEvents(expectedEvents));
+  }, "A pointerout should be fired on the capturing element after losing the capture");
+}, {once: true});
+</script>
+</head>
+<body>
+  <div id="listener">div id=listener</div>
+  <div id="target">div id=target</div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/declarative-shadow-dom-write-to-iframe.html b/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/declarative-shadow-dom-write-to-iframe.html
new file mode 100644
index 0000000..ab0af58
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shadow-dom/declarative/declarative-shadow-dom-write-to-iframe.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>`document.write` on inner iframe handles declarative shadow DOM</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe></iframe>
+<script>
+promise_test(async () => {
+  await new Promise(res => window.addEventListener("load", res));
+  let elem = document.querySelector("iframe");
+  elem.contentDocument.write(`
+    <div>
+      <template shadowrootmode="open"><slot></slot></template>
+      <p>Test</p>
+    </div>
+  `);
+  let container = elem.contentDocument.querySelector("div");
+  assert_true(!!container, "write should occur");
+  assert_true(!!container.shadowRoot, "write should create shadowroot");
+  assert_equals(container.innerText, "Test", "div should still contain text");
+}, "`document.write` on inner iframe handles declarative shadow DOM");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-drawImage-hbd.any.js b/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-drawImage-hbd.any.js
new file mode 100644
index 0000000..f03fce7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-drawImage-hbd.any.js
@@ -0,0 +1,31 @@
+// META: global=window,dedicatedworker
+// META: script=/webcodecs/utils.js
+
+test(_ => {
+  let width = 48;
+  let height = 36;
+  let expectedPixel = kSRGBPixel;
+  let canvasOptions = undefined;
+  let imageSetting = undefined;
+  let tolerance = 5;
+  let vfInit =
+      {format: 'I420P10', timestamp: 0, codedWidth: width, codedHeight: height};
+  let data = new Uint16Array(3 * width * height / 2);
+  let uOffset = width * height;
+  let vOffset = uOffset + width * height / 4;
+  // RGB(50, 100, 150) converted to 8-bit YCbCr using BT.709 YUV matrix, then
+  // shifted to produce approximate 10-bit YUV colors. It would be more accurate
+  // to directly compute 10-bit colors.
+  data.fill(96 << 2, 0, uOffset);
+  data.fill(155 << 2, uOffset, vOffset);
+  data.fill(104 << 2, vOffset);
+  let frame = new VideoFrame(data, vfInit);
+  let canvas = new OffscreenCanvas(width, height);
+  let ctx = canvas.getContext('2d', canvasOptions);
+  ctx.drawImage(frame, 0, 0);
+  testCanvas(ctx, width, height, expectedPixel, imageSetting,
+             (actual, expected) => {
+                 assert_approx_equals(actual, expected, tolerance);
+             });
+  frame.close();
+}, 'drawImage with 10-bit YUV VideoFrame');
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/external/permissions/set_permission/set_permission.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/external/permissions/set_permission/set_permission.py
index 18f8e6f..574f21498 100644
--- a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/external/permissions/set_permission/set_permission.py
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/external/permissions/set_permission/set_permission.py
@@ -100,41 +100,3 @@
         origin=origin,
     )
     assert await get_permission_state(bidi_session, new_tab, "geolocation") == "prompt"
-
-
-@pytest.mark.asyncio
-async def test_set_permission_user_context(bidi_session, new_tab, url, create_user_context):
-    test_url = url("/common/blank.html", protocol="https")
-
-    user_context = await create_user_context()
-    # new_tab is in the default user context. new_tab2 is in the non-default user context.
-    new_tab2 = await bidi_session.browsing_context.create(type_hint="tab", user_context=user_context)
-
-    # Navigate a context in the default user context.
-    await bidi_session.browsing_context.navigate(
-        context=new_tab["context"],
-        url=test_url,
-        wait="complete",
-    )
-
-    # Navigate a context in the non-default user context.
-    await bidi_session.browsing_context.navigate(
-        context=new_tab2["context"],
-        url=test_url,
-        wait="complete",
-    )
-
-    origin = await get_context_origin(bidi_session, new_tab)
-
-    assert await get_permission_state(bidi_session, new_tab, "geolocation") == "prompt"
-    assert await get_permission_state(bidi_session, new_tab2, "geolocation") == "prompt"
-
-    await bidi_session.permissions.set_permission(
-        descriptor={"name": "geolocation"},
-        state="granted",
-        origin=origin,
-        user_context=user_context,
-    )
-
-    assert await get_permission_state(bidi_session, new_tab, "geolocation") == "prompt"
-    assert await get_permission_state(bidi_session, new_tab2, "geolocation") == "granted"
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/external/permissions/set_permission/user_context.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/external/permissions/set_permission/user_context.py
new file mode 100644
index 0000000..b45ddb1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/external/permissions/set_permission/user_context.py
@@ -0,0 +1,131 @@
+import pytest
+
+from . import get_context_origin, get_permission_state
+
+pytestmark = pytest.mark.asyncio
+
+
+async def test_set_permission_user_context(
+    bidi_session, new_tab, url, create_user_context
+):
+    test_url = url("/common/blank.html", protocol="https", domain="alt")
+
+    user_context = await create_user_context()
+    # new_tab is in the default user context. new_tab2 is in the non-default user context.
+    new_tab2 = await bidi_session.browsing_context.create(
+        type_hint="tab", user_context=user_context
+    )
+
+    # Navigate a context in the default user context.
+    await bidi_session.browsing_context.navigate(
+        context=new_tab["context"],
+        url=test_url,
+        wait="complete",
+    )
+
+    # Navigate a context in the non-default user context.
+    await bidi_session.browsing_context.navigate(
+        context=new_tab2["context"],
+        url=test_url,
+        wait="complete",
+    )
+
+    origin = await get_context_origin(bidi_session, new_tab)
+
+    assert await get_permission_state(bidi_session, new_tab, "geolocation") == "prompt"
+    assert await get_permission_state(bidi_session, new_tab2, "geolocation") == "prompt"
+
+    await bidi_session.permissions.set_permission(
+        descriptor={"name": "geolocation"},
+        state="granted",
+        origin=origin,
+        user_context=user_context,
+    )
+
+    assert await get_permission_state(bidi_session, new_tab, "geolocation") == "prompt"
+    assert (
+        await get_permission_state(bidi_session, new_tab2, "geolocation") == "granted"
+    )
+
+    # Create another tab in the non-default user context
+    new_tab3 = await bidi_session.browsing_context.create(
+        type_hint="tab", user_context=user_context
+    )
+    await bidi_session.browsing_context.navigate(
+        context=new_tab3["context"],
+        url=test_url,
+        wait="complete",
+    )
+
+    # Make sure that the permission state is still present.
+    assert (
+        await get_permission_state(bidi_session, new_tab3, "geolocation") == "granted"
+    )
+
+
+async def test_set_permission_with_reload(bidi_session, url, create_user_context):
+    user_context = await create_user_context()
+    new_tab = await bidi_session.browsing_context.create(
+        type_hint="tab", user_context=user_context
+    )
+
+    test_url = url("/common/blank.html", protocol="https")
+    await bidi_session.browsing_context.navigate(
+        context=new_tab["context"],
+        url=test_url,
+        wait="complete",
+    )
+
+    origin = await get_context_origin(bidi_session, new_tab)
+
+    await bidi_session.permissions.set_permission(
+        descriptor={"name": "geolocation"},
+        state="granted",
+        origin=origin,
+        user_context=user_context,
+    )
+
+    assert await get_permission_state(bidi_session, new_tab, "geolocation") == "granted"
+
+    await bidi_session.browsing_context.reload(
+        context=new_tab["context"],
+        wait="complete",
+    )
+
+    # Make sure that permission state is still set.
+    assert await get_permission_state(bidi_session, new_tab, "geolocation") == "granted"
+
+
+async def test_reset_permission(bidi_session, url, create_user_context):
+    test_url = url("/common/blank.html", protocol="https")
+
+    user_context = await create_user_context()
+    new_tab = await bidi_session.browsing_context.create(
+        type_hint="tab", user_context=user_context
+    )
+
+    await bidi_session.browsing_context.navigate(
+        context=new_tab["context"],
+        url=test_url,
+        wait="complete",
+    )
+
+    origin = await get_context_origin(bidi_session, new_tab)
+
+    await bidi_session.permissions.set_permission(
+        descriptor={"name": "geolocation"},
+        state="granted",
+        origin=origin,
+        user_context=user_context,
+    )
+
+    assert await get_permission_state(bidi_session, new_tab, "geolocation") == "granted"
+
+    # Reset the permission state
+    await bidi_session.permissions.set_permission(
+        descriptor={"name": "geolocation"},
+        state="prompt",
+        origin=origin,
+        user_context=user_context,
+    )
+    assert await get_permission_state(bidi_session, new_tab, "geolocation") == "prompt"
diff --git a/third_party/blink/web_tests/external/wpt/webnn/resources/test_data/gather.json b/third_party/blink/web_tests/external/wpt/webnn/resources/test_data/gather.json
index acd7ad87..a0165463 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/resources/test_data/gather.json
+++ b/third_party/blink/web_tests/external/wpt/webnn/resources/test_data/gather.json
@@ -51,56 +51,6 @@
       }
     },
     {
-      "name": "gather float32 1D tensor and int32 0D scalar indices default options",
-      "inputs": {
-        "input": {
-          "shape": [24],
-          "data": [
-            -66.05901336669922,
-            -68.9197006225586,
-            -77.02045440673828,
-            -26.158037185668945,
-            89.0337142944336,
-            -45.89653396606445,
-            43.84803771972656,
-            48.81806945800781,
-            51.79948425292969,
-            41.94132614135742,
-            -1.1303654909133911,
-            -50.42131042480469,
-            90.2870101928711,
-            55.620765686035156,
-            44.92119598388672,
-            56.828636169433594,
-            10.829925537109375,
-            -19.693084716796875,
-            -37.696800231933594,
-            43.11057662963867,
-            0.9129875898361206,
-            -7.699817180633545,
-            25.76774024963379,
-            73.60064697265625
-          ],
-          "type": "float32"
-        },
-        "indices": {
-          "shape": [],
-          "data": [
-            4
-          ],
-          "type": "int32"
-        }
-      },
-      "expected": {
-        "name": "output",
-        "shape": [],
-        "data": [
-          89.0337142944336
-        ],
-        "type": "float32"
-      }
-    },
-    {
       "name": "gather float32 1D tensor and int64 0D scalar indices default options",
       "inputs": {
         "input": {
diff --git a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/gather.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/gather.https.any.js
index 9608243..184e803 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/gather.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/gather.https.any.js
@@ -8,7 +8,7 @@
   {
     name: '[gather] Test gather with default options and 0-D indices',
     input: {dataType: 'int32', dimensions: [3]},
-    indices: {dataType: 'int64', dimensions: []},
+    indices: {dataType: 'uint64', dimensions: []},
     output: {dataType: 'int32', dimensions: []}
   },
   {
diff --git a/third_party/chromite b/third_party/chromite
index 5ff757e..8d0f67b 160000
--- a/third_party/chromite
+++ b/third_party/chromite
@@ -1 +1 @@
-Subproject commit 5ff757e963883d28db6d5b9856e5c7fcc63cdcb0
+Subproject commit 8d0f67b603baecf2da3e1ad830062e356430f5ab
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index 83915e3..c90f785 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit 83915e39be4ead9df32167feafc7c2639d603d5a
+Subproject commit c90f785907da5911ca61f1116990ad6a7fdb6f67
diff --git a/third_party/crabbyavif/BUILD.gn b/third_party/crabbyavif/BUILD.gn
index b2b3c12..14a5813d 100644
--- a/third_party/crabbyavif/BUILD.gn
+++ b/third_party/crabbyavif/BUILD.gn
@@ -133,7 +133,6 @@
     "src/src/capi/mod.rs",
     "src/src/capi/reformat.rs",
     "src/src/capi/types.rs",
-    "src/src/capi/utils.rs",
     "src/src/codecs/dav1d.rs",
     "src/src/codecs/mod.rs",
     "src/src/decoder/gainmap.rs",
diff --git a/third_party/crabbyavif/src b/third_party/crabbyavif/src
index ef17807..2a37e62 160000
--- a/third_party/crabbyavif/src
+++ b/third_party/crabbyavif/src
@@ -1 +1 @@
-Subproject commit ef17807890f60bee1398a752d53204c369076aca
+Subproject commit 2a37e62739815ac00d1195e88135e8b1e5f38c87
diff --git a/third_party/dawn b/third_party/dawn
index d4d6d18a..724dd785 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit d4d6d18aec677d0e43edac057c0595ed3973f4b7
+Subproject commit 724dd7855543eb1d4c75c517de7b576b092bfd7f
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 274689c..e0038c0 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 274689c4a55104b92bf66248d03249ce0175aa02
+Subproject commit e0038c0721935fc3d8119e02c19553f2fee8f8fd
diff --git a/third_party/jni_zero/jni_zero.h b/third_party/jni_zero/jni_zero.h
index 2827f95..3461850 100644
--- a/third_party/jni_zero/jni_zero.h
+++ b/third_party/jni_zero/jni_zero.h
@@ -750,7 +750,7 @@
 
 template <typename T>
 concept IsObjectContainer =
-    IsContainer<T> && !std::integral<typename T::value_type>;
+    IsContainer<T> && !std::is_arithmetic_v<typename T::value_type>;
 }  // namespace internal
 
 // Partial specialization for converting java arrays into std containers
diff --git a/third_party/jni_zero/test/sample_for_tests.cc b/third_party/jni_zero/test/sample_for_tests.cc
index c332f48e..5841ffb 100644
--- a/third_party/jni_zero/test/sample_for_tests.cc
+++ b/third_party/jni_zero/test/sample_for_tests.cc
@@ -17,6 +17,7 @@
 namespace jni_zero::internal {
 static_assert(IsContainer<std::vector<std::string>>);
 static_assert(!IsObjectContainer<std::vector<char>>);
+static_assert(!IsObjectContainer<std::vector<float>>);
 static_assert(!IsObjectContainer<std::string>);
 static_assert(IsObjectContainer<std::vector<std::string>>);
 static_assert(IsObjectContainer<std::vector<std::string*>>);
diff --git a/third_party/lit/v3_0/BUILD.gn b/third_party/lit/v3_0/BUILD.gn
index 850660b..0167d6f 100644
--- a/third_party/lit/v3_0/BUILD.gn
+++ b/third_party/lit/v3_0/BUILD.gn
@@ -19,6 +19,7 @@
     # - a few chrome/browser/resources/ folders  that hold small UIs.
     # Update when the migration enters its next phase.
     "//chrome/browser/resources/side_panel/customize_chrome:build_ts",
+    "//chrome/browser/resources/side_panel/history_clusters:build_ts",
     "//chrome/browser/resources/side_panel/reading_list:build_ts",
     "//chrome/browser/resources/side_panel/shared:build_ts",
     "//chrome/browser/resources/welcome:build_ts",
diff --git a/third_party/openscreen/src b/third_party/openscreen/src
index 6a097c7..a74a035 160000
--- a/third_party/openscreen/src
+++ b/third_party/openscreen/src
@@ -1 +1 @@
-Subproject commit 6a097c705fc4bb3d79bcb4a45f71b7a198ba40f8
+Subproject commit a74a035f778762b1c31ae1e9393eefdbf07be714
diff --git a/third_party/pdfium b/third_party/pdfium
index 547d961..f98c54d 160000
--- a/third_party/pdfium
+++ b/third_party/pdfium
@@ -1 +1 @@
-Subproject commit 547d96118caf46c07b3e520a971992b6909696bd
+Subproject commit f98c54df162cd2a9324dad9e5b9453425311527d
diff --git a/third_party/perfetto b/third_party/perfetto
index 3f851ca..e03af0c 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 3f851caa743d632b338f79ee30e9ae2929eed22d
+Subproject commit e03af0caee1bf0b7ece55e42e607d738f65f9c7b
diff --git a/third_party/polymer/v3_0/BUILD.gn b/third_party/polymer/v3_0/BUILD.gn
index bbf2c65..b6d6d2e 100644
--- a/third_party/polymer/v3_0/BUILD.gn
+++ b/third_party/polymer/v3_0/BUILD.gn
@@ -137,8 +137,6 @@
     "//chrome/browser/resources/settings_shared:build_ts",
     "//chrome/browser/resources/side_panel/bookmarks:build_ts",
     "//chrome/browser/resources/side_panel/commerce:build_ts",
-    "//chrome/browser/resources/side_panel/customize_chrome:build_ts",
-    "//chrome/browser/resources/side_panel/history_clusters:build_ts",
     "//chrome/browser/resources/side_panel/performance_controls:build_ts",
     "//chrome/browser/resources/side_panel/read_anything:build_ts",
     "//chrome/browser/resources/side_panel/shared:build_ts",
diff --git a/third_party/robolectric/3pp/fetch.py b/third_party/robolectric/3pp/fetch.py
index 647c9e6..14e0c55 100755
--- a/third_party/robolectric/3pp/fetch.py
+++ b/third_party/robolectric/3pp/fetch.py
@@ -38,33 +38,6 @@
         'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/6.0.1_r3-robolectric-r1-i6/android-all-instrumented-6.0.1_r3-robolectric-r1-i6.jar',
     'android-all-instrumented-5.0.2_r3-robolectric-r0-i6.jar':
         'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/5.0.2_r3-robolectric-r0-i6/android-all-instrumented-5.0.2_r3-robolectric-r0-i6.jar',
-    # i4 versions left in until migration to robolectric 4.12.1 is complete.
-    'android-all-instrumented-14-robolectric-10818077-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/14-robolectric-10818077-i4/android-all-instrumented-14-robolectric-10818077-i4.jar',
-    'android-all-instrumented-13-robolectric-9030017-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/13-robolectric-9030017-i4/android-all-instrumented-13-robolectric-9030017-i4.jar',
-    'android-all-instrumented-12.1-robolectric-8229987-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/12.1-robolectric-8229987-i4/android-all-instrumented-12.1-robolectric-8229987-i4.jar',
-    'android-all-instrumented-12-robolectric-7732740-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/12-robolectric-7732740-i4/android-all-instrumented-12-robolectric-7732740-i4.jar',
-    'android-all-instrumented-11-robolectric-6757853-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/11-robolectric-6757853-i4/android-all-instrumented-11-robolectric-6757853-i4.jar',
-    'android-all-instrumented-10-robolectric-5803371-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/10-robolectric-5803371-i4/android-all-instrumented-10-robolectric-5803371-i4.jar',
-    'android-all-instrumented-9-robolectric-4913185-2-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/9-robolectric-4913185-2-i4/android-all-instrumented-9-robolectric-4913185-2-i4.jar',
-    'android-all-instrumented-8.1.0-robolectric-4611349-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/8.1.0-robolectric-4611349-i4/android-all-instrumented-8.1.0-robolectric-4611349-i4.jar',
-    'android-all-instrumented-8.0.0_r4-robolectric-r1-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/8.0.0_r4-robolectric-r1-i4/android-all-instrumented-8.0.0_r4-robolectric-r1-i4.jar',
-    'android-all-instrumented-7.1.0_r7-robolectric-r1-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/7.1.0_r7-robolectric-r1-i4/android-all-instrumented-7.1.0_r7-robolectric-r1-i4.jar',
-    'android-all-instrumented-7.0.0_r1-robolectric-r1-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/7.0.0_r1-robolectric-r1-i4/android-all-instrumented-7.0.0_r1-robolectric-r1-i4.jar',
-    'android-all-instrumented-6.0.1_r3-robolectric-r1-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/6.0.1_r3-robolectric-r1-i4/android-all-instrumented-6.0.1_r3-robolectric-r1-i4.jar',
-    'android-all-instrumented-5.0.2_r3-robolectric-r0-i4.jar':
-        'https://repo1.maven.org/maven2/org/robolectric/android-all-instrumented/5.0.2_r3-robolectric-r0-i4/android-all-instrumented-5.0.2_r3-robolectric-r0-i4.jar',
 }
 
 
diff --git a/third_party/robolectric/OWNERS b/third_party/robolectric/OWNERS
index 10989b97..596e1a0 100644
--- a/third_party/robolectric/OWNERS
+++ b/third_party/robolectric/OWNERS
@@ -1,3 +1,4 @@
 agrieve@chromium.org
 bjoyce@chromium.org
+mheikal@chromium.org
 wnwen@chromium.org
diff --git a/third_party/skia b/third_party/skia
index 0bbcd81..530fa37 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 0bbcd8152d32b349e5eee99b29bccc5fcd58ce9b
+Subproject commit 530fa373b797bf1ade172ef591134ea6cc401271
diff --git a/third_party/wayland-protocols/BUILD.gn b/third_party/wayland-protocols/BUILD.gn
index db15b95e..68b46ea 100644
--- a/third_party/wayland-protocols/BUILD.gn
+++ b/third_party/wayland-protocols/BUILD.gn
@@ -196,3 +196,7 @@
     "src/unstable/xdg-shell/xdg-shell-unstable-v6.xml",
   ]
 }
+
+wayland_protocol("xdg_toplevel_drag_protocol") {
+  sources = [ "src/staging/xdg-toplevel-drag/xdg-toplevel-drag-v1.xml" ]
+}
diff --git a/third_party/webrtc b/third_party/webrtc
index b25c747..2192284 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit b25c747d5d318827869fe8304bd975285b33bf57
+Subproject commit 21922847462881016e05b008ffbf7d25c602074b
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index caab53f4..f301fbc 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -10034,6 +10034,12 @@
   </description>
 </action>
 
+<action
+    name="ExplicitBrowserSigninPreferenceRemembered_IPHPromo_SettingsPageOpened">
+  <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
+  <description>Please enter the description of the metric.</description>
+</action>
+
 <action name="ExploreSites.ContextMenu">
   <owner>chili@chromium.org</owner>
   <owner>dewittj@chromium.org</owner>
@@ -42737,6 +42743,8 @@
       label="For Download toolbar button feature."/>
   <suffix name="EphemeralTab" label="For Ephemeral Tab."/>
   <suffix name="ExperimentalAIPromo" label="For Experimental AI feature."/>
+  <suffix name="ExplicitBrowserSigninPreferenceRemembered"
+      label="For Explicit Browser Signin Preference Remembered feature."/>
   <suffix name="ExploreSitesTile" label="For Explore Sites feature."/>
   <suffix name="ExtensionsMenu" label="For Extensions menu opening."/>
   <suffix name="ExtensionsRequestAccessButton"
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 7254dbc..10aaee8 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -16759,6 +16759,7 @@
   <int value="-1980328793" label="trace-upload-url"/>
   <int value="-1978625006" label="PcieBillboardNotification:disabled"/>
   <int value="-1978116081" label="SeaPen:disabled"/>
+  <int value="-1978003156" label="EnableExtensibleEnterpriseSSO:disabled"/>
   <int value="-1977496883" label="ViewPasswords:enabled"/>
   <int value="-1976050618" label="AppDeduplicationService:disabled"/>
   <int value="-1975970208" label="(Obsolete) TranslateSubFrames:disabled"/>
@@ -18668,6 +18669,8 @@
   <int value="-1151014496" label="NearbySharingDeviceContacts:disabled"/>
   <int value="-1150980351" label="CrosWebAppInstallDialog:enabled"/>
   <int value="-1150485573" label="HistoryClustersPersistedClusters:enabled"/>
+  <int value="-1149710408"
+      label="WebAuthenticationEnclaveAuthenticator:enabled"/>
   <int value="-1148764533"
       label="CrOSLateBootAudioAecRequiredForCrasProcessor:enabled"/>
   <int value="-1148269796" label="ProjectorUseUSMForS3:disabled"/>
@@ -19009,6 +19012,7 @@
   <int value="-998731974" label="WinUseBrowserSpellChecker:enabled"/>
   <int value="-998310305" label="OmniboxContextMenuShowFullUrls:disabled"/>
   <int value="-998255750" label="ExperimentalKeyboardLockUI:enabled"/>
+  <int value="-997652636" label="EnableExtensibleEnterpriseSSO:enabled"/>
   <int value="-997491100" label="ShareUsageRanking:disabled"/>
   <int value="-997185008" label="LensFullscreenSearch:disabled"/>
   <int value="-996868396" label="SmartDimNewMlAgent:enabled"/>
@@ -19764,6 +19768,8 @@
   <int value="-657808907" label="CopyLinkToText:disabled"/>
   <int value="-657656374" label="AppListBubble:disabled"/>
   <int value="-656743717" label="HelpAppDiscoverTab:enabled"/>
+  <int value="-656630541"
+      label="WebAuthenticationEnclaveAuthenticator:disabled"/>
   <int value="-655608107" label="MediaAppCustomColors:enabled"/>
   <int value="-655294302"
       label="UseMlServiceForNonLongformHandwritingOnAllBoards:disabled"/>
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml
index cad59bc..ca5d5f1 100644
--- a/tools/metrics/histograms/metadata/android/enums.xml
+++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -536,6 +536,7 @@
   <int value="101" label="WebViewCompat.isAudioMuted"/>
   <int value="102" label="WebSettingsCompat.setWebauthnSupport"/>
   <int value="103" label="WebSettingsCompat.getWebauthnSupport"/>
+  <int value="104" label="WebSettingsCompat.setPreloadingEnabled"/>
 </enum>
 
 <enum name="ArmCpuPart">
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 96e0e6a0..d56b88fe 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -5608,12 +5608,15 @@
 </histogram>
 
 <histogram name="Android.WebView.InputStreamTime" units="ms"
-    expires_after="2023-12-04">
+    expires_after="2024-11-16">
   <owner>jam@chromium.org</owner>
   <owner>cduvall@chromium.org</owner>
+  <owner>stefanoduo@google.com</owner>
   <summary>
     Measures how long InputStreams take from creation to completion. This is
-    emitted for all streams whether they succeed or fail.
+    emitted for all streams whether they succeed or fail. This metric was
+    expired between December 2023 and May 2024. Data from that period will be
+    incomplete.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 8d8ae04..4b8a15d3 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -4855,7 +4855,7 @@
 </histogram>
 
 <histogram name="Ash.Mahi.ButtonClicked" enum="PanelButton"
-    expires_after="2025-04-04">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -4874,7 +4874,7 @@
 </histogram>
 
 <histogram name="Ash.Mahi.QuestionAnswer.LoadingTime" units="ms"
-    expires_after="2025-04-10">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -4904,7 +4904,7 @@
 </histogram>
 
 <histogram name="Ash.Mahi.Summary.LoadingTime" units="ms"
-    expires_after="2025-04-10">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -4914,7 +4914,7 @@
 </histogram>
 
 <histogram name="Ash.Mahi.UserJourneyTime" units="ms"
-    expires_after="2025-04-10">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -4971,7 +4971,7 @@
 </histogram>
 
 <histogram name="Ash.MessageCenter.Scroll.PresentationTime" units="ms"
-    expires_after="2024-10-01">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -4981,7 +4981,7 @@
 </histogram>
 
 <histogram name="Ash.MessageCenter.Scroll.PresentationTime.MaxLatency"
-    units="ms" expires_after="2025-04-16">
+    units="ms" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5126,7 +5126,7 @@
 </histogram>
 
 <histogram name="Ash.Notification.ClearAllStacked.AnimationSmoothness"
-    units="%" expires_after="2024-11-03">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5138,7 +5138,7 @@
 </histogram>
 
 <histogram name="Ash.Notification.ClearAllVisible.AnimationSmoothness"
-    units="%" expires_after="2024-11-03">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5149,7 +5149,7 @@
 </histogram>
 
 <histogram name="Ash.Notification.CountOfNotificationsInOneGroup"
-    units="Notifications" expires_after="2024-11-12">
+    units="Notifications" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5159,7 +5159,7 @@
 </histogram>
 
 <histogram name="Ash.Notification.ExpandOrCollapse.AnimationSmoothness"
-    units="%" expires_after="2024-11-03">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5183,7 +5183,7 @@
 </histogram>
 
 <histogram name="Ash.Notification.GroupNotificationAdded"
-    enum="GroupNotificationType" expires_after="2025-01-14">
+    enum="GroupNotificationType" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5203,7 +5203,7 @@
 </histogram>
 
 <histogram name="Ash.Notification.MoveDown.AnimationSmoothness" units="%"
-    expires_after="2024-11-03">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5235,7 +5235,7 @@
 </histogram>
 
 <histogram name="Ash.Notification.SwipeControl.FadeIn.AnimationSmoothness"
-    units="%" expires_after="2025-01-28">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5246,7 +5246,7 @@
 </histogram>
 
 <histogram name="Ash.NotificationPopup.AnimationSmoothness" units="%"
-    expires_after="2024-09-22">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>amehfooz@chromium.org</owner>
   <owner>tbarzic@chromium.org</owner>
@@ -5259,7 +5259,7 @@
 </histogram>
 
 <histogram name="Ash.NotificationPopup.OnTopOfSurfacesPopupCount"
-    units="popups" expires_after="2024-08-22">
+    units="popups" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5271,7 +5271,7 @@
 </histogram>
 
 <histogram name="Ash.NotificationPopup.OnTopOfSurfacesType"
-    enum="NotifierCollisionSurfaceType" expires_after="2024-08-22">
+    enum="NotifierCollisionSurfaceType" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5284,7 +5284,7 @@
 
 <histogram
     name="Ash.NotificationView.ConvertSingleToGroup.{Animation}.AnimationSmoothness"
-    units="%" expires_after="2024-05-14">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5300,7 +5300,7 @@
 
 <histogram
     name="Ash.NotificationView.ExpandButton.BoundsChange.AnimationSmoothness"
-    units="%" expires_after="2025-01-28">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5311,7 +5311,7 @@
 </histogram>
 
 <histogram name="Ash.NotificationView.ExpandButton.ClickAction"
-    enum="ExpandButtonClickAction" expires_after="2025-04-30">
+    enum="ExpandButtonClickAction" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5323,7 +5323,7 @@
 
 <histogram
     name="Ash.NotificationView.ExpandButton.ConvertSingleToGroup.{Animation}.AnimationSmoothness"
-    units="%" expires_after="2024-05-14">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5340,7 +5340,7 @@
 
 <histogram
     name="Ash.NotificationView.ImageContainerView.ScaleDown.AnimationSmoothness"
-    units="%" expires_after="2025-01-28">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5371,7 +5371,7 @@
 </histogram>
 
 <histogram name="Ash.NotificationView.NotificationAdded.Type"
-    enum="NotificationViewType" expires_after="2024-10-20">
+    enum="NotificationViewType" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5381,7 +5381,7 @@
 </histogram>
 
 <histogram name="Ash.NotificationView.{ChildView}.FadeIn.AnimationSmoothness"
-    units="%" expires_after="2025-01-28">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5406,7 +5406,7 @@
 </histogram>
 
 <histogram name="Ash.NotificationView.{ChildView}.FadeOut.AnimationSmoothness"
-    units="%" expires_after="2025-01-28">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -5430,7 +5430,7 @@
 
 <histogram
     name="Ash.NotificationView.{ChildView}.ScaleAndTranslate.AnimationSmoothness"
-    units="%" expires_after="2024-06-02">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6529,7 +6529,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.AnimationSmoothness" units="%"
-    expires_after="2024-09-19">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6541,7 +6541,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.AppAccessUpdate.Type" enum="AppType"
-    expires_after="2024-09-29">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6552,7 +6552,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.IndicatorShowsDuration" units="ms"
-    expires_after="2024-11-19">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6562,7 +6562,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.LaunchSettings" enum="AppType"
-    expires_after="2024-09-29">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6573,7 +6573,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.NumberOfAppsAccessingCamera"
-    units="count" expires_after="2025-01-21">
+    units="count" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6583,7 +6583,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.NumberOfAppsAccessingMicrophone"
-    units="count" expires_after="2024-09-19">
+    units="count" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6593,7 +6593,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.NumberOfRepeatedShows" units="shows"
-    expires_after="2025-03-24">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6604,7 +6604,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.NumberOfShowsPerSession" units="shows"
-    expires_after="2025-01-21">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6614,7 +6614,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.ShowType" enum="PrivacyIndicatorsType"
-    expires_after="2024-05-26">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6624,7 +6624,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.Source" enum="PrivacyIndicatorsSource"
-    expires_after="2024-09-15">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -6634,7 +6634,7 @@
 </histogram>
 
 <histogram name="Ash.PrivacyIndicators.{Icon}.AnimationSmoothness" units="%"
-    expires_after="2024-09-19">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8068,7 +8068,7 @@
 </histogram>
 
 <histogram name="Ash.StatusArea.TrayBackgroundView.BounceIn" units="%"
-    expires_after="2024-11-03">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8080,7 +8080,7 @@
 </histogram>
 
 <histogram name="Ash.StatusArea.TrayBackgroundView.FadeIn" units="%"
-    expires_after="2025-02-05">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8092,7 +8092,7 @@
 </histogram>
 
 <histogram name="Ash.StatusArea.TrayBackgroundView.Hide" units="%"
-    expires_after="2024-11-03">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8122,7 +8122,7 @@
 </histogram>
 
 <histogram name="Ash.StatusArea.TrayItemView.Hide" units="%"
-    expires_after="2024-11-03">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8133,7 +8133,7 @@
 </histogram>
 
 <histogram name="Ash.StatusArea.TrayItemView.Show" units="%"
-    expires_after="2024-11-03">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8144,7 +8144,7 @@
 </histogram>
 
 <histogram name="Ash.StatusAreaShowBubble.PresentationTime" units="ms"
-    expires_after="2024-09-29">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>amehfooz@chromium.org</owner>
   <owner>tbarzic@chromium.org</owner>
@@ -8451,7 +8451,7 @@
 </histogram>
 
 <histogram name="Ash.VideoConference.NumberOfRepeatedShows" units="shows"
-    expires_after="2024-09-22">
+    expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8462,7 +8462,7 @@
 </histogram>
 
 <histogram name="Ash.VideoConference.ReturnToApp.Click"
-    enum="VideoConferenceAppType" expires_after="2024-09-22">
+    enum="VideoConferenceAppType" expires_after="2025-02-01">
   <owner>leandre@google.com</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8473,7 +8473,7 @@
 
 <histogram
     name="Ash.VideoConference.ReturnToAppButton.FadeOut.AnimationSmoothness"
-    units="%" expires_after="2025-04-20">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8485,7 +8485,7 @@
 
 <histogram
     name="Ash.VideoConference.ReturnToAppPanel.BoundsChange.AnimationSmoothness"
-    units="%" expires_after="2025-04-20">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8496,7 +8496,7 @@
 </histogram>
 
 <histogram name="Ash.VideoConference.{ViewName}.FadeIn.AnimationSmoothness"
-    units="%" expires_after="2025-04-20">
+    units="%" expires_after="2025-02-01">
   <owner>leandre@chromium.org</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8511,7 +8511,7 @@
 </histogram>
 
 <histogram name="Ash.VideoConferenceTray.BackgroundBlur.Click"
-    enum="BackgroundBlurState" expires_after="2024-09-22">
+    enum="BackgroundBlurState" expires_after="2025-02-01">
   <owner>leandre@google.com</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8552,7 +8552,7 @@
 </histogram>
 
 <histogram name="Ash.VideoConferenceTray.StopScreenShareButton.Click"
-    enum="BooleanClicked" expires_after="2025-03-16">
+    enum="BooleanClicked" expires_after="2025-02-01">
   <owner>leandre@google.com</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8562,7 +8562,7 @@
 </histogram>
 
 <histogram name="Ash.VideoConferenceTray.ToggleBubbleButton.Click"
-    enum="BooleanOpened" expires_after="2024-09-22">
+    enum="BooleanOpened" expires_after="2025-02-01">
   <owner>leandre@google.com</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8572,7 +8572,7 @@
 </histogram>
 
 <histogram name="Ash.VideoConferenceTray.{Device}MuteButton.Click"
-    enum="BooleanMuted" expires_after="2024-09-22">
+    enum="BooleanMuted" expires_after="2025-02-01">
   <owner>leandre@google.com</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
@@ -8587,7 +8587,7 @@
 </histogram>
 
 <histogram name="Ash.VideoConferenceTray.{EffectName}.Click"
-    enum="BooleanEnabled" expires_after="2024-09-22">
+    enum="BooleanEnabled" expires_after="2025-02-01">
   <owner>leandre@google.com</owner>
   <owner>cros-status-area-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
index 9b0c8a85..8d98ec00 100644
--- a/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
+++ b/tools/metrics/histograms/metadata/feature_engagement/histograms.xml
@@ -163,6 +163,9 @@
   <variant name="IPH_EphemeralTab"
       summary="new label on the context menu for Ephemeral Tab"/>
   <variant name="IPH_ExperimentalAIPromo" summary="Experimental AI promo"/>
+  <variant name="IPH_ExplicitBrowserSigninPreferenceRemembered"
+      summary="Confirmation message that the explicit browser signin
+               preference was remembered"/>
   <variant name="IPH_ExploreSitesTile" summary="Explore Sites feature"/>
   <variant name="IPH_ExtensionsMenu" summary="extensions menu opened"/>
   <variant name="IPH_ExtensionsRequestAccessButton"
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 6fd65197a..5a39504 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1375,18 +1375,6 @@
   </summary>
 </histogram>
 
-<histogram name="GPU.SharedImage.IsRG88HardwareGMBSupported" enum="Boolean"
-    expires_after="2024-10-06">
-  <owner>hitawala@chromium.org</owner>
-  <owner>graphics-dev@chromium.org</owner>
-  <summary>
-    Tracks if shared images are created with hardware GpuMemoryBuffers of RG88
-    format on Ozone based platforms. Result is a bool that reports if RG88
-    format is supported for creating shared images with hardware GMBs. This is
-    logged once per gpu process launch.
-  </summary>
-</histogram>
-
 <histogram name="GPU.SkiaBackendType" enum="SkiaBackendType"
     expires_after="2025-02-01">
   <owner>kylechar@chromium.org</owner>
@@ -1707,6 +1695,26 @@
 </histogram>
 
 <histogram
+    name="GPU.{GraphiteDawnOrWebGPU}.{Cacheable}.{CacheHitMiss}.90SecondsPostStartup"
+    units="microseconds" expires_after="2024-12-29">
+  <owner>hitawala@chromium.org</owner>
+  <owner>mdb.webgpu-dev-team@google.com</owner>
+  <summary>
+    Tracks the amount of time in microseconds it takes for a cached call to
+    {Cacheable}.{CacheHitMiss} to complete without error when we have a cache
+    hit or miss within 90 seconds of browser start up. Based off the
+    {Cacheable}.CacheHit and {Cacheable}.CacheMiss metrics above. Recorded only
+    for clients that support high-resolution clocks.
+  </summary>
+  <token key="Cacheable" variants="WebGPUCacheable"/>
+  <token key="GraphiteDawnOrWebGPU" variants="GraphiteDawnOrWebGPU"/>
+  <token key="CacheHitMiss">
+    <variant name="CacheHit"/>
+    <variant name="CacheMiss"/>
+  </token>
+</histogram>
+
+<histogram
     name="GPU.{GraphiteDawnOrWebGPU}.{Cacheable}.{CacheHitMiss}.Counts.90SecondsPostStartup"
     units="count" expires_after="2024-12-29">
   <owner>hitawala@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/enums.xml b/tools/metrics/histograms/metadata/others/enums.xml
index 40d2bbc..6d3da1e4 100644
--- a/tools/metrics/histograms/metadata/others/enums.xml
+++ b/tools/metrics/histograms/metadata/others/enums.xml
@@ -57,7 +57,7 @@
   <int value="10" label="Unexpected side panel open"/>
 </enum>
 
-<!-- LINT.ThenChange(//chrome/browser/ui/lens/lens_overlay_controller.h:DismissalSource) -->
+<!-- LINT.ThenChange(//chrome/browser/ui/lens/lens_overlay_dismissal_source.h:LensOverlayDismissalSource) -->
 
 <!-- LINT.IfChange(LensOverlayInvocationSource) -->
 
@@ -71,7 +71,7 @@
   <int value="5" label="Omnibox button"/>
 </enum>
 
-<!-- LINT.ThenChange(//chrome/browser/ui/lens/lens_overlay_controller.h:InvocationSource) -->
+<!-- LINT.ThenChange(//chrome/browser/ui/lens/lens_overlay_invocation_source.h:LensOverlayInvocationSource) -->
 
 <!-- LINT.IfChange(LensPermissionBubbleUserAction) -->
 
diff --git a/tools/metrics/histograms/metadata/performance_controls/histograms.xml b/tools/metrics/histograms/metadata/performance_controls/histograms.xml
index 1146d94..672e88e 100644
--- a/tools/metrics/histograms/metadata/performance_controls/histograms.xml
+++ b/tools/metrics/histograms/metadata/performance_controls/histograms.xml
@@ -76,6 +76,17 @@
   </summary>
 </histogram>
 
+<histogram name="PerformanceControls.MemorySaver.DiscardRingTreatment"
+    enum="BooleanEnabled" expires_after="2025-05-07">
+  <owner>charlesmeng@chromium.org</owner>
+  <owner>chrome-performance-ui-sea@google.com</owner>
+  <summary>
+    Logs whether the inactive tabs appearance pref is enabled, whenever the user
+    makes a change to it through the settings page. This will be used to measure
+    how many users opt out of seeing the discard ring on inactive tabs.
+  </summary>
+</histogram>
+
 <histogram name="PerformanceControls.MemorySaver.IPHEnableMode"
     enum="BooleanEnabled" expires_after="2024-09-22">
   <owner>agale@chromium.org</owner>
diff --git a/tools/origin_trials/generate_token.py b/tools/origin_trials/generate_token.py
index fedfe93..5009aa3 100755
--- a/tools/origin_trials/generate_token.py
+++ b/tools/origin_trials/generate_token.py
@@ -122,10 +122,22 @@
   return "{0}://{1}:{2}".format(origin.scheme, origin.hostname, port)
 
 def ExpiryFromArgs(args):
+  expiry: int
   if args.expire_timestamp:
-    return int(args.expire_timestamp)
-  return (int(time.time()) + (int(args.expire_days) * 86400))
+    expiry = int(args.expire_timestamp)
+  else:
+    expiry = (int(time.time()) + (int(args.expire_days) * 86400))
 
+  if expiry > 2**31 - 1:
+    # The maximum expiry timestamp is bound by the maximum value of a signed
+    # 32-bit integer (2^31-1).
+    # TODO(crbug.com/40872096): All expiries after 2038-01-19 03:14:07 UTC
+    # will raise this error, so add support for a larger range of values
+    # before then.
+    raise argparse.ArgumentTypeError(
+        "%d (%s UTC) is beyond the range of supported expiries" %
+        (expiry, datetime.utcfromtimestamp(expiry)))
+  return expiry
 
 def GenerateTokenData(version, origin, is_subdomain, is_third_party,
                       usage_restriction, feature_name, expiry):
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index 27ec922..f4a7b294 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -39,7 +39,8 @@
                is_calibration=False,
                run_reference_build=False,
                pinpoint_only=False,
-               executables=None):
+               executables=None,
+               crossbench=None):
     benchmark_configs = benchmark_configs.Frozenset()
     self._name = name
     self._description = description
@@ -51,6 +52,7 @@
     self.run_reference_build = run_reference_build
     self.pinpoint_only = pinpoint_only
     self.executables = executables or frozenset()
+    self.crossbench = crossbench or frozenset()
     assert num_shards
     self._num_shards = num_shards
     # pylint: disable=redefined-outer-name
@@ -193,6 +195,16 @@
     self.repeat = 1
 
 
+class CrossbenchConfig:
+
+  def __init__(self, name, crossbench_name, estimated_runtime=60, stories=None):
+    self.name = name
+    self.crossbench_name = crossbench_name
+    self.estimated_runtime = estimated_runtime
+    self.stories = stories or ['default']
+    self.repeat = 1
+
+
 class PerfSuite(object):
   def __init__(self, configs):
     self._configs = dict()
@@ -339,6 +351,17 @@
                           flags=['--xvfb'],
                           estimated_runtime=estimated_runtime)
 
+
+def _crossbench_speedometer3_0(estimated_runtime=60):
+  return CrossbenchConfig('speedometer3.crossbench',
+                          'speedometer_3.0',
+                          estimated_runtime=estimated_runtime)
+
+
+_CROSSBENCH_BENCHMARKS = frozenset([
+    _crossbench_speedometer3_0(),
+])
+
 _CHROME_HEALTH_BENCHMARK_CONFIGS_DESKTOP = PerfSuite([
     _GetBenchmarkConfig('system_health.common_desktop')
 ])
@@ -591,7 +614,8 @@
     _FUCHSIA_PERF_NELSON_BENCHMARK_CONFIGS
 _LINUX_PERF_FYI_BENCHMARK_CONFIGS = PerfSuite([
     _GetBenchmarkConfig('speedometer2'),
-    _GetBenchmarkConfig('speedometer2-nominorms')
+    _GetBenchmarkConfig('speedometer2-nominorms'),
+    _GetBenchmarkConfig('speedometer3'),
 ])
 _LINUX_PERF_CALIBRATION_BENCHMARK_CONFIGS = PerfSuite([
     _GetBenchmarkConfig('speedometer2'),
@@ -890,8 +914,9 @@
 LINUX_PERF_FYI = PerfPlatform('linux-perf-fyi',
                               '',
                               _LINUX_PERF_FYI_BENCHMARK_CONFIGS,
-                              1,
+                              4,
                               'linux',
+                              crossbench=_CROSSBENCH_BENCHMARKS,
                               is_fyi=True)
 
 # Calibration bots
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 3c79d2b..1220a8a 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -220,7 +220,7 @@
         'linux',
         'dimension': {
             'gpu': '10de',
-            'os': 'Ubuntu-22.04',
+            'os': 'Ubuntu',
             'pool': 'chrome.tests.perf-fyi',
         },
     },
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 630a1dc..8bcafa4 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v45.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "73ee58519851534c5248eb6511311bbe2fe931e6",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/44b3d8456c4a942e862bfcf14d998f5f3ff0d2c4/trace_processor_shell.exe"
+            "hash": "4bb514760a289123a95ee6e3a9665a38e6d06575",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/e03af0caee1bf0b7ece55e42e607d738f65f9c7b/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "f7cc2e856e9ee1260e9691c078f3771193eb4dea",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v45.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "a794e3fe62d056ac972131ef0ba3bc290fa983a7",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/26055b167a94a8483779c807e5869ce5f8505f7c/trace_processor_shell"
+            "hash": "4481ef5b3d14b6baa6bf69daa8e0bd1759d2a783",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/e03af0caee1bf0b7ece55e42e607d738f65f9c7b/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/shard_maps/linux-perf-fyi_map.json b/tools/perf/core/shard_maps/linux-perf-fyi_map.json
index b3062d041..b9b1bdb 100644
--- a/tools/perf/core/shard_maps/linux-perf-fyi_map.json
+++ b/tools/perf/core/shard_maps/linux-perf-fyi_map.json
@@ -1,20 +1,85 @@
 {
     "0": {
         "benchmarks": {
+            "speedometer2": {
+                "abridged": false
+            },
             "speedometer2-nominorms": {
                 "abridged": false
             },
+            "speedometer3": {
+                "abridged": false
+            }
+        },
+        "crossbench": {
+            "speedometer3.crossbench": {
+                "crossbench_name": "speedometer_3.0"
+            }
+        }
+    },
+    "1": {
+        "benchmarks": {
             "speedometer2": {
                 "abridged": false
+            },
+            "speedometer2-nominorms": {
+                "abridged": false
+            },
+            "speedometer3": {
+                "abridged": false
+            }
+        },
+        "crossbench": {
+            "speedometer3.crossbench": {
+                "crossbench_name": "speedometer_3.0"
+            }
+        }
+    },
+    "2": {
+        "benchmarks": {
+            "speedometer2": {
+                "abridged": false
+            },
+            "speedometer2-nominorms": {
+                "abridged": false
+            },
+            "speedometer3": {
+                "abridged": false
+            }
+        },
+        "crossbench": {
+            "speedometer3.crossbench": {
+                "crossbench_name": "speedometer_3.0"
+            }
+        }
+    },
+    "3": {
+        "benchmarks": {
+            "speedometer2": {
+                "abridged": false
+            },
+            "speedometer2-nominorms": {
+                "abridged": false
+            },
+            "speedometer3": {
+                "abridged": false
+            }
+        },
+        "crossbench": {
+            "speedometer3.crossbench": {
+                "crossbench_name": "speedometer_3.0"
             }
         }
     },
     "extra_infos": {
-        "num_stories": 2,
-        "predicted_min_shard_time": 20,
+        "num_stories": 16,
+        "predicted_min_shard_time": 90,
         "predicted_min_shard_index": 0,
-        "predicted_max_shard_time": 20,
+        "predicted_max_shard_time": 90,
         "predicted_max_shard_index": 0,
-        "shard #0": 20
+        "shard #0": 90,
+        "shard #1": 90,
+        "shard #2": 90,
+        "shard #3": 90
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/sharding_map_generator.py b/tools/perf/core/sharding_map_generator.py
index cd001d7..6095fbea 100644
--- a/tools/perf/core/sharding_map_generator.py
+++ b/tools/perf/core/sharding_map_generator.py
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 import collections
 
+import core.bot_platforms
 import core.path_util
 import core.cli_utils
 
@@ -279,8 +280,18 @@
   # Format the benchmark's stories by indices
   benchmarks_in_shard = collections.OrderedDict()
   executables_in_shard = collections.OrderedDict()
+  crossbench_in_shard = collections.OrderedDict()
   for b in benchmarks:
-    if benchmark_name_to_config[b].is_telemetry:
+    config = benchmark_name_to_config[b]
+    if isinstance(config, core.bot_platforms.CrossbenchConfig):
+      crossbench_in_shard[b] = {'crossbench_name': config.crossbench_name}
+      first_story = all_stories[b].index(benchmarks[b][0])
+      last_story = all_stories[b].index(benchmarks[b][-1]) + 1
+      if first_story != 0:
+        crossbench_in_shard[b]['begin'] = first_story
+      if last_story != len(all_stories[b]):
+        crossbench_in_shard[b]['end'] = last_story
+    elif config.is_telemetry:
       benchmarks_in_shard[b] = {}
       first_story = all_stories[b].index(benchmarks[b][0])
       last_story = all_stories[b].index(benchmarks[b][-1]) + 1
@@ -290,7 +301,6 @@
         benchmarks_in_shard[b]['end'] = last_story
       benchmarks_in_shard[b]['abridged'] = benchmark_name_to_config[b].abridged
     else:
-      config = benchmark_name_to_config[b]
       executables_in_shard[b] = {}
       if config.flags:
         executables_in_shard[b]['arguments'] = config.flags
@@ -300,6 +310,8 @@
     sharding_map[str(shard_index)]['benchmarks'] = benchmarks_in_shard
   if executables_in_shard:
     sharding_map[str(shard_index)]['executables'] = executables_in_shard
+  if crossbench_in_shard:
+    sharding_map[str(shard_index)]['crossbench'] = crossbench_in_shard
 
 
 def _gather_timing_data(benchmarks_to_shard, timing_data, repeat):
@@ -317,7 +329,7 @@
     run_count = b.repeat if repeat else 1
     for s in b.stories:
       test_name = '%s/%s' % (b.name, s)
-      test_duration = DEFAULT_STORY_DURATION
+      test_duration = getattr(b, 'estimated_runtime', DEFAULT_STORY_DURATION)
       if test_name in timing_data_dict:
         test_duration = timing_data_dict[test_name] * run_count
       timing_data_list.append((test_name, test_duration))
diff --git a/tools/perf/core/sharding_map_generator_unittest.py b/tools/perf/core/sharding_map_generator_unittest.py
index 4a3b232..b48ae578 100644
--- a/tools/perf/core/sharding_map_generator_unittest.py
+++ b/tools/perf/core/sharding_map_generator_unittest.py
@@ -8,6 +8,7 @@
 import os
 import unittest
 
+from core import bot_platforms
 from core import sharding_map_generator
 
 
@@ -176,3 +177,16 @@
     self.assertIn('benchmark_1', sharding_map['2']['benchmarks'])
     self.assertIn('benchmark_1', sharding_map['3']['benchmarks'])
     self.assertIn('benchmark_1', sharding_map['4']['benchmarks'])
+
+  def testGenerateShardingMapWithCrossbench(self):
+    benchmarks_data, timing_data, = self._generate_test_data(
+        [[10, 20, 30], [65, 55, 5, 45], [50, 40, 30, 20, 10]])
+    benchmarks_data.append(
+        bot_platforms.CrossbenchConfig('cb_benchmark_0', 'cb_benchmark_0_name'))
+    sharding_map = sharding_map_generator.generate_sharding_map(
+        benchmarks_data, timing_data, 3, None)
+    self.assertIn('crossbench', sharding_map['2'])
+    self.assertIn('cb_benchmark_0', sharding_map['2']['crossbench'])
+    self.assertEqual(
+        'cb_benchmark_0_name',
+        sharding_map['2']['crossbench']['cb_benchmark_0']['crossbench_name'])
diff --git a/tools/perf/cross_device_test_config.py b/tools/perf/cross_device_test_config.py
index 76340f34..d609238 100644
--- a/tools/perf/cross_device_test_config.py
+++ b/tools/perf/cross_device_test_config.py
@@ -147,6 +147,12 @@
             'Speedometer3': 20,
         },
     },
+    'linux-perf-fyi': {
+        'speedometer2': 4,
+        'speedometer2-nominorms': 4,
+        'speedometer3': 4,
+        'speedometer3.crossbench': 4,
+    },
     'win-10_laptop_low_end-perf': {
         'jetstream2': {
             'JetStream2': 5,
diff --git a/tools/perf/generate_perf_sharding.py b/tools/perf/generate_perf_sharding.py
index 72a76224..6ec032b 100755
--- a/tools/perf/generate_perf_sharding.py
+++ b/tools/perf/generate_perf_sharding.py
@@ -147,8 +147,8 @@
   if builder:
     with open(builder.timing_file_path) as f:
       timing_data = json.load(f)
-  benchmarks_to_shard = (
-      list(builder.benchmark_configs) + list(builder.executables))
+  benchmarks_to_shard = (list(builder.benchmark_configs) +
+                         list(builder.executables) + list(builder.crossbench))
   repeat_config = cross_device_test_config.TARGET_DEVICES.get(builder.name, {})
   sharding_map = sharding_map_generator.generate_sharding_map(
       benchmarks_to_shard,
diff --git a/ui/accessibility/accessibility_features.cc b/ui/accessibility/accessibility_features.cc
index 5e98ece..913e8c41 100644
--- a/ui/accessibility/accessibility_features.cc
+++ b/ui/accessibility/accessibility_features.cc
@@ -15,13 +15,6 @@
 
 namespace features {
 
-BASE_FEATURE(kAccessibilityFocusHighlight,
-             "AccessibilityFocusHighlight",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-bool IsAccessibilityFocusHighlightEnabled() {
-  return base::FeatureList::IsEnabled(::features::kAccessibilityFocusHighlight);
-}
-
 BASE_FEATURE(kAccessibilityPdfOcrForSelectToSpeak,
              "kAccessibilityPdfOcrForSelectToSpeak",
 #if BUILDFLAG(IS_CHROMEOS)
diff --git a/ui/accessibility/accessibility_features.h b/ui/accessibility/accessibility_features.h
index a2b620e3..479602c9 100644
--- a/ui/accessibility/accessibility_features.h
+++ b/ui/accessibility/accessibility_features.h
@@ -43,11 +43,6 @@
 
 namespace features {
 
-// 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();
-
 // Enable PDF OCR for Select-to-Speak. It will be disabled by default on
 // platforms other than ChromeOS as STS is available only on ChromeOS.
 AX_BASE_EXPORT BASE_DECLARE_FEATURE(kAccessibilityPdfOcrForSelectToSpeak);
diff --git a/ui/accessibility/extensions/BUILD.gn b/ui/accessibility/extensions/BUILD.gn
index 288e141..72d6e20 100644
--- a/ui/accessibility/extensions/BUILD.gn
+++ b/ui/accessibility/extensions/BUILD.gn
@@ -23,7 +23,6 @@
   testonly = true
   if (is_chromeos_ash) {
     deps = [
-      ":caretbrowsing_tests",
       ":colorenhancer_tests",
       ":highcontrast_tests",
       "chromevoxclassic:chromevox_tests",
@@ -286,17 +285,7 @@
   "caretbrowsing/caret_19_on.png",
   "caretbrowsing/caret_19.png",
   "caretbrowsing/caret_48.png",
-  "caretbrowsing/caretbrowsing.css",
-  "caretbrowsing/caretbrowsing.js",
-  "caretbrowsing/increase_brightness.png",
   "caretbrowsing/manifest.json",
-  "caretbrowsing/node_util.js",
-  "caretbrowsing/options.html",
-  "caretbrowsing/options.js",
-  "caretbrowsing/selection_util.js",
-  "caretbrowsing/storage.js",
-  "caretbrowsing/traverse_util.js",
-  "//third_party/accessibility-audit/axs_testing.js",
 ]
 
 copy("caretbrowsing_copy") {
@@ -305,33 +294,6 @@
 }
 
 if (is_chromeos_ash) {
-  test("caretbrowsing_tests") {
-    deps = [ ":caretbrowsing_webui_js_tests" ]
-    deps += webui_test_deps
-
-    data = js2gtest_js_libraries
-  }
-
-  js2gtest("caretbrowsing_webui_js_tests") {
-    test_type = "webui"
-    sources = [
-      "caretbrowsing/node_util_test.js",
-      "caretbrowsing/selection_util_test.js",
-      "caretbrowsing/storage_test.js",
-    ]
-    gen_include_files = [
-      "caretbrowsing/node_util.js",
-      "caretbrowsing/selection_util.js",
-      "caretbrowsing/storage.js",
-      "caretbrowsing/traverse_util.js",
-      "testing/webstore_extension_test_base.js",
-      "//chrome/browser/resources/chromeos/accessibility/common/testing/callback_helper.js",
-      "//chrome/browser/resources/chromeos/accessibility/common/testing/mock_storage.js",
-    ]
-
-    defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
-  }
-
   test("highcontrast_tests") {
     deps = [ ":highcontrast_webui_js_tests" ]
     deps += webui_test_deps
diff --git a/ui/accessibility/extensions/caretbrowsing/background.js b/ui/accessibility/extensions/caretbrowsing/background.js
index 3c9d7f7..1ed94b6 100644
--- a/ui/accessibility/extensions/caretbrowsing/background.js
+++ b/ui/accessibility/extensions/caretbrowsing/background.js
@@ -6,82 +6,5 @@
  * @fileoverview Script that runs on the background page.
  */
 
-importScripts('storage.js');
-Storage.initialize();
-
-CONTENT_SCRIPTS = [
-  'accessibility_utils.js', 'node_util.js', 'selection_util.js',
-  'traverse_util.js', 'storage.js', 'caret_browsing.js'
-];
-
-/**
- * The class handling the Caret Browsing background page, which keeps
- * track of the current state, handles the browser action button, and
- * initializes the content script in all running tabs when the extension
- * is first loaded.
- * @constructor
- */
-const CaretBkgnd = function() {};
-
-/**
- * Change the browser action icon and tooltip based on the enabled state.
- */
-CaretBkgnd.setIcon = function() {
-  chrome.action.setIcon(
-      {'path': Storage.enabled ?
-               '../caret_19_on.png' :
-               '../caret_19.png'});
-  chrome.action.setTitle(
-      {'title': Storage.enabled ?
-                'Turn Off Caret Browsing (F7)' :
-                'Turn On Caret Browsing (F7)' });
-};
-
-/**
- * This is called when the extension is first loaded, so that it can be
- * immediately used in all already-open tabs. It's not needed for any
- * new tabs that open after that, the content script will be automatically
- * injected into any new tab.
- */
-CaretBkgnd.injectContentScripts = function() {
-  chrome.windows.getAll({'populate': true}, function(windows) {
-    for (const w of windows) {
-      for (const tab of w.tabs) {
-        chrome.scripting.executeScript(
-            {
-              target: {tabId: tab.id, allFrames: true},
-              files: CONTENT_SCRIPTS,
-            },
-            function(result) {
-              // Ignore.
-              chrome.runtime.lastError;
-            });
-      }
-    }
-  });
-};
-
-/**
- * Toggle caret browsing on or off, and update the browser action icon and
- * all open tabs.
- */
-CaretBkgnd.toggle = function() {
-  Storage.enabled = !Storage.enabled;
-  CaretBkgnd.setIcon();
-};
-
-/**
- * Initialize the background script. Set the initial value of the flag
- * based on the saved preference in localStorage, update the browser action,
- * inject into running tabs, and then set up communication with content
- * scripts in tabs. Also check for prefs updates (from the options page)
- * and send them to content scripts.
- */
-CaretBkgnd.init = function() {
-  CaretBkgnd.setIcon();
-  chrome.action.onClicked.addListener(CaretBkgnd.toggle);
-  Storage.ENABLED.listeners.push(CaretBkgnd.setIcon);
-};
-
-CaretBkgnd.init();
-self.addEventListener('install', CaretBkgnd.injectContentScripts);
+console.log('Caret browsing has been built into the browser! You can ' +
+    'continue to use it exactly as you have been.');
\ No newline at end of file
diff --git a/ui/accessibility/extensions/caretbrowsing/caretbrowsing.css b/ui/accessibility/extensions/caretbrowsing/caretbrowsing.css
deleted file mode 100644
index e39dbd4..0000000
--- a/ui/accessibility/extensions/caretbrowsing/caretbrowsing.css
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Copyright 2014 The Chromium Authors
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file. */
-
-.CaretBrowsing_Caret {
-  position: absolute;
-  z-index: 2147483647;
-  min-height: 10px;
-  background-color: #000;
-}
-
-.CaretBrowsing_AnimateCaret {
-  position: absolute;
-  z-index: 2147483647;
-  min-height: 10px;
-}
-
-.CaretBrowsing_FlashVert {
-  position: absolute;
-  z-index: 2147483647;
-  background: linear-gradient(
-      270deg,
-      rgba(128, 128, 255, 0) 0%,
-      rgba(128, 128, 255, 0.3) 45%,
-      rgba(128, 128, 255, 0.8) 50%,
-      rgba(128, 128, 255, 0.3) 65%,
-      rgba(128, 128, 255, 0) 100%);
-}
diff --git a/ui/accessibility/extensions/caretbrowsing/caretbrowsing.js b/ui/accessibility/extensions/caretbrowsing/caretbrowsing.js
deleted file mode 100644
index 47e0c25..0000000
--- a/ui/accessibility/extensions/caretbrowsing/caretbrowsing.js
+++ /dev/null
@@ -1,1079 +0,0 @@
-/* Copyright 2014 The Chromium Authors
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file. */
-
-/**
- * @fileoverview Caret browsing content script, runs in each frame.
- *
- * The behavior is based on Mozilla's spec whenever possible:
- *   http://www.mozilla.org/access/keyboard/proposal
- *
- * The one exception is that Esc is used to escape out of a form control,
- * rather than their proposed key (which doesn't seem to work in the
- * latest Firefox anyway).
- *
- * Some details about how Chrome selection works, which will help in
- * understanding the code:
- *
- * The Selection object (window.getSelection()) has four components that
- * completely describe the state of the caret or selection:
- *
- * base and anchor: this is the start of the selection, the fixed point.
- * extent and focus: this is the end of the selection, the part that
- *     moves when you hold down shift and press the left or right arrows.
- *
- * When the selection is a cursor, the base, anchor, extent, and focus are
- * all the same.
- *
- * There's only one time when the base and anchor are not the same, or the
- * extent and focus are not the same, and that's when the selection is in
- * an ambiguous state - i.e. it's not clear which edge is the focus and which
- * is the anchor. As an example, if you double-click to select a word, then
- * the behavior is dependent on your next action. If you press Shift+Right,
- * the right edge becomes the focus. But if you press Shift+Left, the left
- * edge becomes the focus.
- *
- * When the selection is in an ambiguous state, the base and extent are set
- * to the position where the mouse clicked, and the anchor and focus are set
- * to the boundaries of the selection.
- *
- * The only way to set the selection and give it direction is to use
- * the non-standard Selection.setBaseAndExtent method. If you try to use
- * Selection.addRange(), the anchor will always be on the left and the focus
- * will always be on the right, making it impossible to manipulate
- * selections that move from right to left.
- *
- * Finally, Chrome will throw an exception if you try to set an invalid
- * selection - a selection where the left and right edges are not the same,
- * but it doesn't span any visible characters. A common example is that
- * there are often many whitespace characters in the DOM that are not
- * visible on the page; trying to select them will fail. Another example is
- * any node that's invisible or not displayed.
- *
- * While there are probably many possible methods to determine what is
- * selectable, this code uses the method of determining if there's a valid
- * bounding box for the range or not - keep moving the cursor forwards until
- * the range from the previous position and candidate next position has a
- * valid bounding box.
- */
-
-Storage.initialize();
-
-/**
- * The class handling the Caret Browsing implementation in the page.
- * Installs a keydown listener that always responds to the F7 key,
- * sets up communication with the background page, and then when caret
- * browsing is enabled, response to various key events to move the caret
- * or selection within the text content of the document. Uses the native
- * Chrome selection wherever possible, but displays its own flashing
- * caret using a DIV because there's no native caret available.
- * @constructor
- */
-class CaretBrowsing {
-  constructor() {
-    /**
-     * Tracks whether to keep caret browsing enabled on this page even when it's
-     * flipped off. This is used on the options page.
-     * @type {boolean}
-     */
-    this.forceEnabled = false;
-
-    /**
-     * Tracks whether this window / iframe is focused. The caret isn't shown on
-     * pages that are not focused, which is especially important so that carets
-     * aren't shown in two iframes of the same tab.
-     * @type {boolean}
-     */
-    this.isWindowFocused = false;
-
-    /**
-     * Tracks whether the caret is actually visible. This is true only if
-     * Storage.enabled and this.isWindowFocused are both true.
-     * @type {boolean}
-     */
-    this.isCaretVisible = false;
-
-    /**
-     * The actual caret HTML element, an absolute-positioned flashing line.
-     * @type {Element}
-     */
-    this.caretElement;
-
-    /**
-     * The x-position of the caret, in absolute pixels.
-     * @type {number}
-     */
-    this.caretX = 0;
-
-    /**
-     * The y-position of the caret, in absolute pixels.
-     * @type {number}
-     */
-    this.caretY = 0;
-
-    /**
-     * The width of the caret in pixels.
-     * @type {number}
-     */
-    this.caretWidth = 0;
-
-    /**
-     * The height of the caret in pixels.
-     * @type {number}
-     */
-    this.caretHeight = 0;
-
-    /**
-     * The caret's foreground color.
-     * @type {string}
-     */
-    this.caretForeground = '#000';
-
-    /**
-     * The caret's background color.
-     * @type {string}
-     */
-    this.caretBackground = '#fff';
-
-    /**
-     * Tracks whether the selection is collapsed, i.e. are the start and end
-     * locations the same? If so, our blinking caret image is shown; otherwise
-     * the Chrome selection is shown.
-     * @type {boolean}
-     */
-    this.isSelectionCollapsed = false;
-
-    /**
-     * The id returned by window.setInterval for our blink function, so
-     * we can cancel it when caret browsing is disabled.
-     * @type {?number}
-     */
-    this.blinkFunctionId = null;
-
-    /**
-     * The desired x-coordinate to match when moving the caret up and down.
-     * To match the behavior as documented in Mozilla's caret browsing spec
-     * (http://www.mozilla.org/access/keyboard/proposal), we keep track of the
-     * initial x position when the user starts moving the caret up and down,
-     * so that the x position doesn't drift as you move throughout lines, but
-     * stays as close as possible to the initial position. This is reset when
-     * moving left or right or clicking.
-     * @type {?number}
-     */
-    this.targetX = null;
-
-    /**
-     * A flag that flips on or off as the caret blinks.
-     * @type {boolean}
-     */
-    this.blinkFlag = true;
-
-    /**
-     * Whether or not we're on a Mac - which affects modifier keys.
-     * @type {boolean}
-     */
-    this.isMac = (navigator.appVersion.indexOf("Mac") != -1);
-
-    this.init();
-  }
-
-  /**
-   * If there's no initial selection, set the cursor just before the
-   * first text character in the document.
-   */
-  setInitialCursor() {
-    const sel = window.getSelection();
-    if (sel.rangeCount > 0) {
-      return;
-    }
-
-    const start = new Cursor(document.body, 0, '');
-    const end = new Cursor(document.body, 0, '');
-    const nodesCrossed = [];
-    const result = TraverseUtil.getNextChar(start, end, nodesCrossed, true);
-    if (result == null) {
-      return;
-    }
-    SelectionUtil.setAndValidateSelection(start, start);
-  }
-
-  /**
-   * Set the caret element's normal style, i.e. not when animating.
-   */
-  setCaretElementNormalStyle() {
-    const element = this.caretElement;
-    element.className = 'CaretBrowsing_Caret';
-    element.style.opacity = this.isSelectionCollapsed ? '1.0' : '0.0';
-    element.style.left = this.caretX + 'px';
-    element.style.top = this.caretY + 'px';
-    element.style.width = this.caretWidth + 'px';
-    element.style.height = this.caretHeight + 'px';
-    element.style.color = this.caretForeground;
-  }
-
-  /**
-   * Animate the caret element into the normal style.
-   */
-  animateCaretElement() {
-    const element = this.caretElement;
-    element.style.left = (this.caretX - 50) + 'px';
-    element.style.top = (this.caretY - 100) + 'px';
-    element.style.width = (this.caretWidth + 100) + 'px';
-    element.style.height = (this.caretHeight + 200) + 'px';
-    element.className = 'CaretBrowsing_AnimateCaret';
-
-    // Start the animation. The setTimeout is so that the old values will get
-    // applied first, so we can animate to the new values.
-    window.setTimeout(() => {
-      if (!this.caretElement) {
-        return;
-      }
-      this.setCaretElementNormalStyle();
-      element.style['transition'] = 'all 0.8s ease-in';
-      const listener = () => {
-        element.removeEventListener(
-            'transitionend', listener, false);
-        element.style['transition'] = 'none';
-      }
-      element.addEventListener(
-          'transitionend', listener, false);
-    }, 0);
-  }
-
-  /**
-   * Quick flash and then show the normal caret style.
-   */
-  flashCaretElement() {
-    const x = this.caretX;
-    const y = this.caretY;
-    const height = this.caretHeight;
-
-    const vert = document.createElement('div');
-    vert.className = 'CaretBrowsing_FlashVert';
-    vert.style.left = (x - 6) + 'px';
-    vert.style.top = (y - 100) + 'px';
-    vert.style.width = '11px';
-    vert.style.height = (200) + 'px';
-    document.body.appendChild(vert);
-
-    window.setTimeout(() => {
-      document.body.removeChild(vert);
-      if (this.caretElement) {
-        this.setCaretElementNormalStyle();
-      }
-    }, 250);
-  }
-
-  /**
-   * Create the caret element. This assumes that caretX, caretY,
-   * caretWidth, and caretHeight have all been set. The caret is
-   * animated in so the user can find it when it first appears.
-   */
-  createCaretElement() {
-    const element = document.createElement('div');
-    element.className = 'CaretBrowsing_Caret';
-    document.body.appendChild(element);
-    this.caretElement = element;
-
-    if (Storage.onEnable === FlourishType.ANIMATE) {
-      this.animateCaretElement();
-    } else if (Storage.onEnable === FlourishType.FLASH) {
-      this.flashCaretElement();
-    } else {
-      this.setCaretElementNormalStyle();
-    }
-  }
-
-  /**
-   * Recreate the caret element, triggering any intro animation.
-   */
-  recreateCaretElement() {
-    if (this.caretElement) {
-      window.clearInterval(this.blinkFunctionId);
-      this.caretElement.parentElement.removeChild(
-          this.caretElement);
-      this.caretElement = null;
-      this.updateIsCaretVisible();
-    }
-  }
-
-  /**
-   * Compute the new location of the caret or selection and update
-   * the element as needed.
-   * @param {boolean} scrollToSelection If true, will also scroll the page
-   *     to the caret / selection location.
-   */
-  updateCaretOrSelection(scrollToSelection) {
-    const previousX = this.caretX;
-    const previousY = this.caretY;
-
-    const sel = window.getSelection();
-    if (sel.rangeCount == 0) {
-      if (this.caretElement) {
-        this.isSelectionCollapsed = false;
-        this.caretElement.style.opacity = '0.0';
-      }
-      return;
-    }
-
-    const range = sel.getRangeAt(0);
-    if (!range) {
-      if (this.caretElement) {
-        this.isSelectionCollapsed = false;
-        this.caretElement.style.opacity = '0.0';
-      }
-      return;
-    }
-
-    if (NodeUtil.isControlThatNeedsArrowKeys(document.activeElement)) {
-      let node = document.activeElement;
-      this.caretWidth = node.offsetWidth;
-      this.caretHeight = node.offsetHeight;
-      this.caretX = 0;
-      this.caretY = 0;
-      while (node.offsetParent) {
-        this.caretX += node.offsetLeft;
-        this.caretY += node.offsetTop;
-        node = node.offsetParent;
-      }
-      this.isSelectionCollapsed = false;
-    } else if (
-        range.startOffset != range.endOffset ||
-        range.startContainer != range.endContainer) {
-      const rect = range.getBoundingClientRect();
-      if (!rect) {
-        return;
-      }
-      this.caretX = rect.left + window.pageXOffset;
-      this.caretY = rect.top + window.pageYOffset;
-      this.caretWidth = rect.width;
-      this.caretHeight = rect.height;
-      this.isSelectionCollapsed = false;
-    } else {
-      const rect = SelectionUtil.getCursorRect(new Cursor(
-          range.startContainer, range.startOffset,
-          TraverseUtil.getNodeText(range.startContainer)));
-      this.caretX = rect.left;
-      this.caretY = rect.top;
-      this.caretWidth = rect.width;
-      this.caretHeight = rect.height;
-      this.isSelectionCollapsed = true;
-    }
-
-    if (!this.caretElement) {
-      this.createCaretElement();
-    } else {
-      const element = this.caretElement;
-      if (this.isSelectionCollapsed) {
-        element.style.opacity = '1.0';
-        element.style.left = this.caretX + 'px';
-        element.style.top = this.caretY + 'px';
-        element.style.width = this.caretWidth + 'px';
-        element.style.height = this.caretHeight + 'px';
-      } else {
-        element.style.opacity = '0.0';
-      }
-    }
-
-    let elem = range.startContainer;
-    if (elem.constructor == Text)
-      elem = elem.parentElement;
-    const style = window.getComputedStyle(elem);
-    const bg = axs.utils.getBgColor(style, elem);
-    const fg = axs.utils.getFgColor(style, elem, bg);
-    this.caretBackground = axs.color.colorToString(bg);
-    this.caretForeground = axs.color.colorToString(fg);
-
-    if (scrollToSelection) {
-      // Scroll just to the "focus" position of the selection,
-      // the part the user is manipulating.
-      const rect = SelectionUtil.getCursorRect(new Cursor(
-          sel.focusNode, sel.focusOffset,
-          TraverseUtil.getNodeText(sel.focusNode)));
-
-      const yscroll = window.pageYOffset;
-      const pageHeight = window.innerHeight;
-      const caretY = rect.top;
-      const caretHeight = Math.min(rect.height, 30);
-      if (yscroll + pageHeight < caretY + caretHeight) {
-        window.scroll(0, (caretY + caretHeight - pageHeight + 100));
-      } else if (caretY < yscroll) {
-        window.scroll(0, (caretY - 100));
-      }
-    }
-
-    if (Math.abs(previousX - this.caretX) > 500 ||
-        Math.abs(previousY - this.caretY) > 100) {
-      if (Storage.onJump === FlourishType.ANIMATE) {
-        this.animateCaretElement();
-      } else if (Storage.onJump === FlourishType.FLASH) {
-        this.flashCaretElement();
-      }
-    }
-  }
-
-  /**
-   * Determines if the modifier key is held down that should cause
-   * the cursor to move by word rather than by character.
-   * @param {Event} evt A keyboard event.
-   * @return {boolean} True if the cursor should move by word.
-   */
-  isMoveByWordEvent(evt) {
-    if (this.isMac) {
-      return evt.altKey;
-    } else {
-      return evt.ctrlKey;
-    }
-  }
-
-  /**
-   * Moves the cursor forwards to the next valid position.
-   * @param {Cursor} cursor The current cursor location.
-   *     On exit, the cursor will be at the next position.
-   * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
-   *     initial and final cursor position will be pushed onto this array.
-   * @return {?string} The character reached, or null if the bottom of the
-   *     document has been reached.
-   */
-  forwards(cursor, nodesCrossed) {
-    const previousCursor = cursor.clone();
-    const result = TraverseUtil.forwardsChar(cursor, nodesCrossed);
-
-    // Work around the fact that TraverseUtil.forwardsChar returns once per
-    // char in a block of text, rather than once per possible selection
-    // position in a block of text.
-    if (result && cursor.node != previousCursor.node && cursor.index > 0) {
-      cursor.index = 0;
-    }
-
-    return result;
-  }
-
-  /**
-   * Moves the cursor backwards to the previous valid position.
-   * @param {Cursor} cursor The current cursor location.
-   *     On exit, the cursor will be at the previous position.
-   * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
-   *     initial and final cursor position will be pushed onto this array.
-   * @return {?string} The character reached, or null if the top of the
-   *     document has been reached.
-   */
-  backwards(cursor, nodesCrossed) {
-    const previousCursor = cursor.clone();
-    const result = TraverseUtil.backwardsChar(cursor, nodesCrossed);
-
-    // Work around the fact that TraverseUtil.backwardsChar returns once per
-    // char in a block of text, rather than once per possible selection
-    // position in a block of text.
-    if (result &&
-        cursor.node != previousCursor.node &&
-        cursor.index < cursor.text.length) {
-      cursor.index = cursor.text.length;
-    }
-
-    return result;
-  }
-
-  /**
-   * Called when the user presses the right arrow. If there's a selection,
-   * moves the cursor to the end of the selection range. If it's a cursor,
-   * moves past one character.
-   * @param {Event} evt The DOM event.
-   * @return {boolean} True if the default action should be performed.
-   */
-  moveRight(evt) {
-    this.targetX = null;
-
-    const sel = window.getSelection();
-    if (!evt.shiftKey && !SelectionUtil.isCollapsed(sel)) {
-      const right = SelectionUtil.makeRightCursor(sel);
-      SelectionUtil.setAndValidateSelection(right, right);
-      return false;
-    }
-
-    const start = SelectionUtil.isAmbiguous(sel) ?
-        SelectionUtil.makeLeftCursor(sel) :
-        SelectionUtil.makeAnchorCursor(sel);
-    const end = SelectionUtil.isAmbiguous(sel) ?
-        SelectionUtil.makeRightCursor(sel) :
-        SelectionUtil.makeFocusCursor(sel);
-    let previousEnd = end.clone();
-    const nodesCrossed = [];
-    while (true) {
-      let result;
-      if (this.isMoveByWordEvent(evt)) {
-        result = TraverseUtil.getNextWord(previousEnd, end, nodesCrossed);
-      } else {
-        previousEnd = end.clone();
-        result = this.forwards(end, nodesCrossed);
-      }
-
-      if (result === null) {
-        return this.moveLeft(evt);
-      }
-
-      if (SelectionUtil.setAndValidateSelection(
-              evt.shiftKey ? start : end, end)) {
-        break;
-      }
-    }
-
-    if (!evt.shiftKey) {
-      nodesCrossed.push(end.node);
-      NodeUtil.setFocusToFirstFocusable(nodesCrossed);
-    }
-
-    return false;
-  }
-
-  /**
-   * Called when the user presses the left arrow. If there's a selection,
-   * moves the cursor to the start of the selection range. If it's a cursor,
-   * moves backwards past one character.
-   * @param {Event} evt The DOM event.
-   * @return {boolean} True if the default action should be performed.
-   */
-  moveLeft(evt) {
-    this.targetX = null;
-
-    const sel = window.getSelection();
-    if (!evt.shiftKey && !SelectionUtil.isCollapsed(sel)) {
-      const left = SelectionUtil.makeLeftCursor(sel);
-      SelectionUtil.setAndValidateSelection(left, left);
-      return false;
-    }
-
-    const start = SelectionUtil.isAmbiguous(sel) ?
-        SelectionUtil.makeLeftCursor(sel) :
-        SelectionUtil.makeFocusCursor(sel);
-    const end = SelectionUtil.isAmbiguous(sel) ?
-        SelectionUtil.makeRightCursor(sel) :
-        SelectionUtil.makeAnchorCursor(sel);
-    let previousStart = start.clone();
-    const nodesCrossed = [];
-    while (true) {
-      let result;
-      if (this.isMoveByWordEvent(evt)) {
-        result = TraverseUtil.getPreviousWord(
-            start, previousStart, nodesCrossed);
-      } else {
-        previousStart = start.clone();
-        result = this.backwards(start, nodesCrossed);
-      }
-
-      if (result === null) {
-        break;
-      }
-
-      if (SelectionUtil.setAndValidateSelection(
-              evt.shiftKey ? end : start, start)) {
-        break;
-      }
-    }
-
-    if (!evt.shiftKey) {
-      nodesCrossed.push(start.node);
-      NodeUtil.setFocusToFirstFocusable(nodesCrossed);
-    }
-
-    return false;
-  }
-
-
-  /**
-   * Called when the user presses the down arrow. If there's a selection,
-   * moves the cursor to the end of the selection range. If it's a cursor,
-   * attempts to move to the equivalent horizontal pixel position in the
-   * subsequent line of text. If this is impossible, go to the first character
-   * of the next line.
-   * @param {Event} evt The DOM event.
-   * @return {boolean} True if the default action should be performed.
-   */
-  moveDown(evt) {
-    const sel = window.getSelection();
-    if (!evt.shiftKey && !SelectionUtil.isCollapsed(sel)) {
-      const right = SelectionUtil.makeRightCursor(sel);
-      SelectionUtil.setAndValidateSelection(right, right);
-      return false;
-    }
-
-    const start = SelectionUtil.isAmbiguous(sel) ?
-        SelectionUtil.makeLeftCursor(sel) :
-        SelectionUtil.makeAnchorCursor(sel);
-    const end = SelectionUtil.isAmbiguous(sel) ?
-        SelectionUtil.makeRightCursor(sel) :
-        SelectionUtil.makeFocusCursor(sel);
-    const endRect = SelectionUtil.getCursorRect(end);
-    if (this.targetX === null) {
-      this.targetX = endRect.left;
-    }
-    const previousEnd = end.clone();
-    let leftPos = end.clone();
-    const rightPos = end.clone();
-    let bestPos = null;
-    let bestY = null;
-    let bestDelta = null;
-    let bestHeight = null;
-    const nodesCrossed = [];
-    let y = -1;
-    while (true) {
-      if (null === this.forwards(rightPos, nodesCrossed)) {
-        if (SelectionUtil.setAndValidateSelection(
-                evt.shiftKey ? start : leftPos, leftPos)) {
-          break;
-        } else {
-          return this.moveLeft(evt);
-        }
-        break;
-      }
-      const range = document.createRange();
-      range.setStart(leftPos.node, leftPos.index);
-      range.setEnd(rightPos.node, rightPos.index);
-      const rect = range.getBoundingClientRect();
-      if (rect && rect.width < rect.height) {
-        y = rect.top + window.pageYOffset;
-
-        // Return the best match so far if we get half a line past the best.
-        if (bestY != null && y > bestY + bestHeight / 2) {
-          if (SelectionUtil.setAndValidateSelection(
-                  evt.shiftKey ? start : bestPos, bestPos)) {
-            break;
-          } else {
-            bestY = null;
-          }
-        }
-
-        // Stop here if we're an entire line the wrong direction
-        // (for example, we reached the top of the next column).
-        if (y < endRect.top - endRect.height) {
-          if (SelectionUtil.setAndValidateSelection(
-                  evt.shiftKey ? start : leftPos, leftPos)) {
-            break;
-          }
-        }
-
-        // Otherwise look to see if this current position is on the
-        // next line and better than the previous best match, if any.
-        if (y >= endRect.top + endRect.height) {
-          const deltaLeft = Math.abs(this.targetX - rect.left);
-          if ((bestDelta == null || deltaLeft < bestDelta) &&
-              (leftPos.node != end.node || leftPos.index != end.index)) {
-            bestPos = leftPos.clone();
-            bestY = y;
-            bestDelta = deltaLeft;
-            bestHeight = rect.height;
-          }
-          const deltaRight = Math.abs(this.targetX - rect.right);
-          if (bestDelta == null || deltaRight < bestDelta) {
-            bestPos = rightPos.clone();
-            bestY = y;
-            bestDelta = deltaRight;
-            bestHeight = rect.height;
-          }
-
-          // Return the best match so far if the deltas are getting worse,
-          // not better.
-          if (bestDelta != null &&
-              deltaLeft > bestDelta &&
-              deltaRight > bestDelta) {
-            if (SelectionUtil.setAndValidateSelection(
-                    evt.shiftKey ? start : bestPos, bestPos)) {
-              break;
-            } else {
-              bestY = null;
-            }
-          }
-        }
-      }
-      leftPos = rightPos.clone();
-    }
-
-    if (!evt.shiftKey) {
-      NodeUtil.setFocusToNode(leftPos.node);
-    }
-
-    return false;
-  }
-
-  /**
-   * Called when the user presses the up arrow. If there's a selection,
-   * moves the cursor to the start of the selection range. If it's a cursor,
-   * attempts to move to the equivalent horizontal pixel position in the
-   * previous line of text. If this is impossible, go to the last character
-   * of the previous line.
-   * @param {Event} evt The DOM event.
-   * @return {boolean} True if the default action should be performed.
-   */
-  moveUp(evt) {
-    const sel = window.getSelection();
-    if (!evt.shiftKey && !SelectionUtil.isCollapsed(sel)) {
-      const left = SelectionUtil.makeLeftCursor(sel);
-      SelectionUtil.setAndValidateSelection(left, left);
-      return false;
-    }
-
-    const start = SelectionUtil.isAmbiguous(sel) ?
-        SelectionUtil.makeLeftCursor(sel) :
-        SelectionUtil.makeFocusCursor(sel);
-    const end = SelectionUtil.isAmbiguous(sel) ?
-        SelectionUtil.makeRightCursor(sel) :
-        SelectionUtil.makeAnchorCursor(sel);
-    const startRect = SelectionUtil.getCursorRect(start);
-    if (this.targetX === null) {
-      this.targetX = startRect.left;
-    }
-    const previousStart = start.clone();
-    const leftPos = start.clone();
-    let rightPos = start.clone();
-    let bestPos = null;
-    let bestY = null;
-    let bestDelta = null;
-    let bestHeight = null;
-    const nodesCrossed = [];
-    let y = 999999;
-    while (true) {
-      if (null === this.backwards(leftPos, nodesCrossed)) {
-        SelectionUtil.setAndValidateSelection(
-            evt.shiftKey ? end : rightPos, rightPos);
-        break;
-      }
-      const range = document.createRange();
-      range.setStart(leftPos.node, leftPos.index);
-      range.setEnd(rightPos.node, rightPos.index);
-      const rect = range.getBoundingClientRect();
-      if (rect && rect.width < rect.height) {
-        y = rect.top + window.pageYOffset;
-
-        // Return the best match so far if we get half a line past the best.
-        if (bestY != null && y < bestY - bestHeight / 2) {
-          if (SelectionUtil.setAndValidateSelection(
-                  evt.shiftKey ? end : bestPos, bestPos)) {
-            break;
-          } else {
-            bestY = null;
-          }
-        }
-
-        // Exit if we're an entire line the wrong direction
-        // (for example, we reached the bottom of the previous column.)
-        if (y > startRect.top + startRect.height) {
-          if (SelectionUtil.setAndValidateSelection(
-                  evt.shiftKey ? end : rightPos, rightPos)) {
-            break;
-          }
-        }
-
-        // Otherwise look to see if this current position is on the
-        // next line and better than the previous best match, if any.
-        if (y <= startRect.top - startRect.height) {
-          const deltaLeft = Math.abs(this.targetX - rect.left);
-          if (bestDelta == null || deltaLeft < bestDelta) {
-            bestPos = leftPos.clone();
-            bestY = y;
-            bestDelta = deltaLeft;
-            bestHeight = rect.height;
-          }
-          const deltaRight = Math.abs(this.targetX - rect.right);
-          if ((bestDelta == null || deltaRight < bestDelta) &&
-              (rightPos.node != start.node || rightPos.index != start.index)) {
-            bestPos = rightPos.clone();
-            bestY = y;
-            bestDelta = deltaRight;
-            bestHeight = rect.height;
-          }
-
-          // Return the best match so far if the deltas are getting worse,
-          // not better.
-          if (bestDelta != null &&
-              deltaLeft > bestDelta &&
-              deltaRight > bestDelta) {
-            if (SelectionUtil.setAndValidateSelection(
-                    evt.shiftKey ? end : bestPos, bestPos)) {
-              break;
-            } else {
-              bestY = null;
-            }
-          }
-        }
-      }
-      rightPos = leftPos.clone();
-    }
-
-    if (!evt.shiftKey) {
-      NodeUtil.setFocusToNode(rightPos.node);
-    }
-
-    return false;
-  }
-
-  /**
-   * Set the document's selection to surround a control, so that the next
-   * arrow key they press will allow them to explore the content before
-   * or after a given control.
-   * @param {Node} control The control to escape from.
-   */
-  escapeFromControl(control) {
-    control.blur();
-
-    let start = new Cursor(control, 0, '');
-    let previousStart = start.clone();
-    let end = new Cursor(control, 0, '');
-    let previousEnd = end.clone();
-
-    const nodesCrossed = [];
-    while (true) {
-      if (null === this.backwards(start, nodesCrossed)) {
-        break;
-      }
-
-      const r = document.createRange();
-      r.setStart(start.node, start.index);
-      r.setEnd(previousStart.node, previousStart.index);
-      if (r.getBoundingClientRect()) {
-        break;
-      }
-      previousStart = start.clone();
-    }
-    while (true) {
-      if (null === this.forwards(end, nodesCrossed)) {
-        break;
-      }
-      if (NodeUtil.isDescendantOfNode(end.node, control)) {
-        previousEnd = end.clone();
-        continue;
-      }
-
-      const r = document.createRange();
-      r.setStart(previousEnd.node, previousEnd.index);
-      r.setEnd(end.node, end.index);
-      if (r.getBoundingClientRect()) {
-        break;
-      }
-    }
-
-    if (!NodeUtil.isDescendantOfNode(previousStart.node, control)) {
-      start = previousStart.clone();
-    }
-
-    if (!NodeUtil.isDescendantOfNode(previousEnd.node, control)) {
-      end = previousEnd.clone();
-    }
-
-    SelectionUtil.setAndValidateSelection(start, end);
-
-    window.setTimeout(() => {
-      this.updateCaretOrSelection(true);
-    }, 0);
-  }
-
-  /**
-   * Toggle whether caret browsing is enabled or not.
-   */
-  toggle() {
-    if (this.forceEnabled) {
-      this.recreateCaretElement();
-      return;
-    }
-
-    Storage.enabled = !Storage.enabled;
-    this.updateIsCaretVisible();
-  }
-
-  /**
-   * Event handler, called when a key is pressed.
-   * @param {Event} evt The DOM event.
-   * @return {boolean} True if the default action should be performed.
-   */
-  onKeyDown(evt) {
-    if (evt.defaultPrevented) {
-      return;
-    }
-
-    if (evt.keyCode == 118) {  // F7
-      this.toggle();
-    }
-
-    if (!Storage.enabled) {
-      return true;
-    }
-
-    if (evt.target &&
-        NodeUtil.isControlThatNeedsArrowKeys(
-            /** @type (Node) */ (evt.target))) {
-      if (evt.keyCode == 27) {
-        this.escapeFromControl(/** @type {Node} */(evt.target));
-        evt.preventDefault();
-        evt.stopPropagation();
-        return false;
-      } else {
-        return true;
-      }
-    }
-
-    // If the current selection doesn't have a range, try to escape out of
-    // the current control. If that fails, return so we don't fail whe
-    // trying to move the cursor or selection.
-    let sel = window.getSelection();
-    if (sel.rangeCount == 0) {
-      if (document.activeElement) {
-        this.escapeFromControl(document.activeElement);
-        sel = window.getSelection();
-      }
-
-      if (sel.rangeCount == 0) {
-        return true;
-      }
-    }
-
-    if (this.caretElement) {
-      this.caretElement.style.visibility = 'visible';
-      this.blinkFlag = true;
-    }
-
-    let result = true;
-    switch (evt.keyCode) {
-      case 37:
-        result = this.moveLeft(evt);
-        break;
-      case 38:
-        result = this.moveUp(evt);
-        break;
-      case 39:
-        result = this.moveRight(evt);
-        break;
-      case 40:
-        result = this.moveDown(evt);
-        break;
-    }
-
-    if (result == false) {
-      evt.preventDefault();
-      evt.stopPropagation();
-    }
-
-    window.setTimeout(() => {
-      this.updateCaretOrSelection(result == false);
-    }, 0);
-
-    return result;
-  }
-
-  /**
-   * Event handler, called when the mouse is clicked. Chrome already
-   * sets the selection when the mouse is clicked, all we need to do is
-   * update our cursor.
-   * @param {Event} evt The DOM event.
-   * @return {boolean} True if the default action should be performed.
-   */
-  onClick(evt) {
-    if (!Storage.enabled) {
-      return true;
-    }
-    window.setTimeout(() => {
-      this.targetX = null;
-      this.updateCaretOrSelection(false);
-    }, 0);
-    return true;
-  }
-
-  /**
-   * Called at a regular interval. Blink the cursor by changing its visibility.
-   */
-  caretBlinkFunction() {
-    if (this.caretElement) {
-      if (this.blinkFlag) {
-        this.caretElement.style.backgroundColor =
-            this.caretForeground;
-        this.blinkFlag = false;
-      } else {
-        this.caretElement.style.backgroundColor =
-            this.caretBackground;
-        this.blinkFlag = true;
-      }
-    }
-  }
-
-  /**
-   * Update whether or not the caret is visible, based on whether caret browsing
-   * is enabled and whether this window / iframe has focus.
-   */
-  updateIsCaretVisible() {
-    this.isCaretVisible =
-        (Storage.enabled && this.isWindowFocused);
-    if (this.isCaretVisible && !this.caretElement) {
-      this.setInitialCursor();
-      this.updateCaretOrSelection(true);
-      if (this.caretElement) {
-        this.blinkFunctionId = window.setInterval(
-            this.caretBlinkFunction, 500);
-      }
-    } else if (!this.isCaretVisible &&
-               this.caretElement) {
-      window.clearInterval(this.blinkFunctionId);
-      if (this.caretElement) {
-        this.isSelectionCollapsed = false;
-        this.caretElement.parentElement.removeChild(
-            this.caretElement);
-        this.caretElement = null;
-      }
-    }
-  }
-
-  /**
-   * Called when the prefs get updated.
-   */
-  onPrefsUpdated() {
-    this.recreateCaretElement();
-  }
-
-  /**
-   * Called when this window / iframe gains focus.
-   */
-  onWindowFocus() {
-    this.isWindowFocused = true;
-    this.updateIsCaretVisible();
-  }
-
-  /**
-   * Called when this window / iframe loses focus.
-   */
-  onWindowBlur() {
-    this.isWindowFocused = false;
-    this.updateIsCaretVisible();
-  }
-
-  /**
-   * Initializes caret browsing by adding event listeners and extension
-   * message listeners.
-   */
-  init() {
-    this.isWindowFocused = document.hasFocus();
-
-    document.addEventListener('keydown', this.onKeyDown.bind(this), false);
-    document.addEventListener('click', this.onClick.bind(this), false);
-    window.addEventListener('focus', this.onWindowFocus.bind(this), false);
-    window.addEventListener('blur', this.onWindowBlur.bind(this), false);
-  }
-}
-
-window.setTimeout(() => {
-
-  // Make sure the script only loads once.
-  if (!window['caretBrowsingLoaded']) {
-    window['caretBrowsingLoaded'] = true;
-    window.caretBrowsing = new CaretBrowsing();
-
-    if (document.body.getAttribute('caretbrowsing') == 'on') {
-      caretBrowsing.forceEnabled = true;
-      Storage.enabled = true;
-      caretBrowsing.updateIsCaretVisible();
-    }
-
-    Storage.ENABLED.listeners.push(() => caretBrowsing.onPrefsUpdated());
-    Storage.ON_ENABLE.listeners.push(() => caretBrowsing.onPrefsUpdated());
-    Storage.ON_JUMP.listeners.push(() => caretBrowsing.onPrefsUpdated());
-
-    caretBrowsing.onPrefsUpdated();
-  }
-
-}, 0);
diff --git a/ui/accessibility/extensions/caretbrowsing/increase_brightness.png b/ui/accessibility/extensions/caretbrowsing/increase_brightness.png
deleted file mode 100644
index 7f427fb9..0000000
--- a/ui/accessibility/extensions/caretbrowsing/increase_brightness.png
+++ /dev/null
Binary files differ
diff --git a/ui/accessibility/extensions/caretbrowsing/manifest.json b/ui/accessibility/extensions/caretbrowsing/manifest.json
index 07299d0..67f963bf 100644
--- a/ui/accessibility/extensions/caretbrowsing/manifest.json
+++ b/ui/accessibility/extensions/caretbrowsing/manifest.json
@@ -1,13 +1,8 @@
 {
   "name": "__MSG_CARET_BROWSING_APPNAME__",
-  "version": "1.0.2",
+  "version": "1.1.0",
   "description": "__MSG_CARET_BROWSING_APPDESC__",
   "manifest_version": 3,
-  "permissions": [
-    "scripting",
-    "storage"
-  ],
-  "host_permissions": [ "<all_urls>" ],
   "background": {
     "service_worker": "background.js"
   },
@@ -15,30 +10,10 @@
     "default_icon": "caret_19.png",
     "default_title": "__MSG_CARET_BROWSING_APPNAME__"
   },
-  "content_scripts": [
-    {
-      "matches": [
-        "<all_urls>"
-      ],
-      "all_frames": true,
-      "css": [
-        "caretbrowsing.css"
-      ],
-      "js": [
-        "axs_testing.js",
-        "traverse_util.js",
-	"node_util.js",
-	"selection_util.js",
-	"storage.js",
-        "caretbrowsing.js"
-      ]
-    }
-  ],
   "default_locale": "en",
   "icons": {
     "16": "caret_16.png",
     "48": "caret_48.png",
     "128": "caret_128.png"
-  },
-  "options_page": "options.html"
+  }
 }
diff --git a/ui/accessibility/extensions/caretbrowsing/node_util.js b/ui/accessibility/extensions/caretbrowsing/node_util.js
deleted file mode 100644
index c87d017..0000000
--- a/ui/accessibility/extensions/caretbrowsing/node_util.js
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** A collection of functions for dealing with DOM nodes. */
-class NodeUtil {
-  /**
-   * Return whether a node is focusable. This includes nodes whose tabindex
-   * attribute is set to "-1" explicitly - these nodes are not in the tab
-   * order, but they should still be focused if the user navigates to them
-   * using linear or smart DOM navigation.
-   *
-   * Note that when the tabIndex property of an Element is -1, that doesn't
-   * tell us whether the tabIndex attribute is missing or set to "-1"
-   * explicitly, so we have to check the attribute.
-   *
-   * @param {Object} targetNode The node to check if it's focusable.
-   * @return {boolean} True if the node is focusable.
-   */
-  static isFocusable(targetNode) {
-    if (!targetNode || typeof (targetNode.tabIndex) != 'number') {
-      return false;
-    }
-
-    if (targetNode.tabIndex >= 0) {
-      return true;
-    }
-
-    if (targetNode.hasAttribute && targetNode.hasAttribute('tabindex') &&
-        targetNode.getAttribute('tabindex') == '-1') {
-      return true;
-    }
-
-    return false;
-  }
-
-  /**
-   * Determines whether or not a node is or is the descendant of another node.
-   *
-   * @param {Object} node The node to be checked.
-   * @param {Object} ancestor The node to see if it's a descendant of.
-   * @return {boolean} True if the node is ancestor or is a descendant of it.
-   */
-  static isDescendantOfNode(node, ancestor) {
-    while (node && ancestor) {
-      if (node.isSameNode(ancestor)) {
-        return true;
-      }
-      node = node.parentNode;
-    }
-    return false;
-  }
-
-  /**
-   * Check if a node is a control that normally allows the user to interact
-   * with it using arrow keys. We won't override the arrow keys when such a
-   * control has focus, the user must press Escape to do caret browsing outside
-   * that control.
-   * @param {Node} node A node to check.
-   * @return {boolean} True if this node is a control that the user can
-   *     interact with using arrow keys.
-   */
-  static isControlThatNeedsArrowKeys(node) {
-    if (!node) {
-      return false;
-    }
-
-    if (node == document.body || node != document.activeElement) {
-      return false;
-    }
-
-    if (node.constructor == HTMLSelectElement) {
-      return true;
-    }
-
-    if (node.constructor == HTMLInputElement) {
-      switch (node.type) {
-        case 'email':
-        case 'number':
-        case 'password':
-        case 'search':
-        case 'text':
-        case 'tel':
-        case 'url':
-        case '':
-          return true;  // All of these are text boxes.
-        case 'datetime':
-        case 'datetime-local':
-        case 'date':
-        case 'month':
-        case 'radio':
-        case 'range':
-        case 'week':
-          return true;  // These are other input elements that use arrows.
-      }
-    }
-
-    // Handle focusable ARIA controls.
-    if (node.getAttribute && NodeUtil.isFocusable(node)) {
-      const role = node.getAttribute('role');
-      switch (role) {
-        case 'combobox':
-        case 'grid':
-        case 'gridcell':
-        case 'listbox':
-        case 'menu':
-        case 'menubar':
-        case 'menuitem':
-        case 'menuitemcheckbox':
-        case 'menuitemradio':
-        case 'option':
-        case 'radiogroup':
-        case 'scrollbar':
-        case 'slider':
-        case 'spinbutton':
-        case 'tab':
-        case 'tablist':
-        case 'textbox':
-        case 'tree':
-        case 'treegrid':
-        case 'treeitem':
-          return true;
-      }
-    }
-
-    return false;
-  }
-
-  /**
-   * Set focus to a node if it's focusable. If it's an input element,
-   * select the text, otherwise it doesn't appear focused to the user.
-   * Every other control behaves normally if you just call focus() on it.
-   * @param {Node} node The node to focus.
-   * @return {boolean} True if the node was focused.
-   */
-  static setFocusToNode(node) {
-    while (node && node != document.body) {
-      if (NodeUtil.isFocusable(node) && node.constructor != HTMLIFrameElement) {
-        node.focus();
-        if (node.constructor == HTMLInputElement && node.select) {
-          node.select();
-        }
-        return true;
-      }
-      node = node.parentNode;
-    }
-
-    return false;
-  }
-
-  /**
-   * Set focus to the first focusable node in the given list.
-   * @param {!Array<!Node>} nodeList An array of nodes to focus.
-   * @return {boolean} True if the node was focused.
-   */
-  static setFocusToFirstFocusable(nodeList) {
-    for (let i = 0; i < nodeList.length; i++) {
-      if (NodeUtil.setFocusToNode(nodeList[i])) {
-        return true;
-      }
-    }
-    return false;
-  }
-}
diff --git a/ui/accessibility/extensions/caretbrowsing/node_util_test.js b/ui/accessibility/extensions/caretbrowsing/node_util_test.js
deleted file mode 100644
index 38825f7..0000000
--- a/ui/accessibility/extensions/caretbrowsing/node_util_test.js
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-GEN_INCLUDE(['node_util.js']);
-
-GEN_INCLUDE(['../testing/webstore_extension_test_base.js']);
-
-/** Test fixture for node_util.js. */
-NodeUtilUnitTest = class extends WebstoreExtensionTest {};
-
-TEST_F('NodeUtilUnitTest', 'IsFocusable', function() {
-  assertFalse(NodeUtil.isFocusable(null));
-
-  // Nodes with no tab index are not focusable.
-  const noTabIndex = document.createElement('div');
-  assertFalse(NodeUtil.isFocusable(noTabIndex));
-
-  // Nodes with a positive number tab index are focusable.
-  const positiveTabIndex = document.createElement('div');
-  positiveTabIndex.tabIndex = 1;
-  assertTrue(NodeUtil.isFocusable(positiveTabIndex));
-
-  // Nodes with a tab index of 0 are focusable.
-  const zeroTabIndex = document.createElement('div');
-  zeroTabIndex.tabIndex = 0;
-  assertTrue(NodeUtil.isFocusable(zeroTabIndex));
-
-  // Some nodes have an implicit non-negative tab index.
-  const button = document.createElement('button');
-  assertTrue(NodeUtil.isFocusable(button));
-
-  // Nodes with a negative tab index that's not -1 are not focusable.
-  const negativeTwoTabIndex = document.createElement('button');
-  negativeTwoTabIndex.tabIndex = -2;
-  assertFalse(NodeUtil.isFocusable(negativeTwoTabIndex));
-
-  // Nodes with an explicitly set tab index of -1 are focusable.
-  const negativeOneDiv = document.createElement('div');
-  negativeOneDiv.tabIndex = -1;
-  assertTrue(NodeUtil.isFocusable(negativeOneDiv));
-
-  const negativeOneButton = document.createElement('button');
-  negativeOneButton.tabIndex = -1;
-  assertTrue(NodeUtil.isFocusable(negativeOneButton));
-});
-
-TEST_F('NodeUtilUnitTest', 'IsDescendantOfNode', function() {
-  const grandparent = document.createElement('div');
-  const parent = document.createElement('p');
-  const child = document.createElement('span');
-  const orphan = document.createElement('img');
-
-  grandparent.appendChild(parent);
-  parent.appendChild(child);
-
-  assertTrue(NodeUtil.isDescendantOfNode(parent, grandparent));
-  assertTrue(NodeUtil.isDescendantOfNode(child, grandparent));
-  assertTrue(NodeUtil.isDescendantOfNode(child, parent));
-
-  assertFalse(NodeUtil.isDescendantOfNode(orphan, parent));
-  assertFalse(NodeUtil.isDescendantOfNode(orphan, grandparent));
-  assertFalse(NodeUtil.isDescendantOfNode(orphan, parent));
-
-  assertFalse(NodeUtil.isDescendantOfNode(grandparent, parent));
-  assertFalse(NodeUtil.isDescendantOfNode(grandparent, child));
-  assertFalse(NodeUtil.isDescendantOfNode(parent, child));
-  assertFalse(NodeUtil.isDescendantOfNode(grandparent, orphan));
-  assertFalse(NodeUtil.isDescendantOfNode(parent, orphan));
-  assertFalse(NodeUtil.isDescendantOfNode(child, orphan));
-});
-
-TEST_F('NodeUtilUnitTest', 'SetFocusToNode', function() {
-  let focusCount;
-  let selectCount;
-
-  // Calling on a focusable node should focus that node.
-  const button = document.createElement('button');
-  button.focus = () => focusCount++;
-  button.select = () => selectCount--;
-
-  focusCount = 0;
-  selectCount = 0;
-  assertTrue(NodeUtil.setFocusToNode(button));
-  assertEquals(1, focusCount);
-  assertEquals(0, selectCount);
-
-  // Calling on an iframe (even a focusable one) should not focus anything.
-  const iframe = document.createElement('iframe');
-  iframe.tabIndex = 0;
-  iframe.focus = () => focusCount--;
-  iframe.select = () => selectCount--;
-
-  focusCount = 0;
-  selectCount = 0;
-  assertFalse(NodeUtil.setFocusToNode(iframe));
-  assertEquals(0, focusCount);
-  assertEquals(0, selectCount);
-
-  // Calling on a focusable input should select the input contents.
-  const input = document.createElement('input');
-  input.focus = () => focusCount++;
-  input.select = () => selectCount++;
-
-  focusCount = 0;
-  selectCount = 0;
-  assertTrue(NodeUtil.setFocusToNode(input));
-  assertEquals(1, focusCount);
-  assertEquals(1, selectCount);
-
-  // Calling on the child of a focusable node should focus the parent.
-  const focusableDiv = document.createElement('div');
-  focusableDiv.tabIndex = 0;
-  focusableDiv.focus = () => focusCount++;
-  focusableDiv.select = () => selectCount--;
-  const p = document.createElement('p');
-  p.focus = () => focusCount--;
-  p.select = () => selectCount--;
-  focusableDiv.appendChild(p);
-
-  focusCount = 0;
-  selectCount = 0;
-  assertTrue(NodeUtil.setFocusToNode(p));
-  assertEquals(1, focusCount);
-  assertEquals(0, selectCount);
-
-  // Calling on a non-focusable node with no ancestors other than document.body
-  // should return false.
-  const img = document.createElement('img');
-  img.focus = () => focusCount--;
-  img.select = () => selectCount--;
-  document.body.appendChild(img);
-  document.body.focus = () => focusCount--;
-  document.body.select = () => selectCount--;
-
-  focusCount = 0;
-  selectCount = 0;
-  assertFalse(NodeUtil.setFocusToNode(img));
-  assertEquals(0, focusCount);
-  assertEquals(0, selectCount);
-});
-
-TEST_F('NodeUtilUnitTest', 'SetFocusToFirstFocusable', function() {
-  let focusACount = 0;
-  let focusPCount = 0;
-  let focusButtonCount = 0;
-
-  const a = document.createElement('a');
-  a.focus = () => focusACount++;
-  const p = document.createElement('p');
-  p.focus = () => focusPCount++;
-  const button = document.createElement('button');
-  button.focus = () => focusButtonCount++;
-
-  assertTrue(NodeUtil.setFocusToFirstFocusable([a, p, button]));
-  assertEquals(1, focusACount);
-  assertEquals(0, focusPCount);
-  assertEquals(0, focusButtonCount);
-
-  focusACount = 0;
-  assertTrue(NodeUtil.setFocusToFirstFocusable([p, button, a]));
-  assertEquals(0, focusACount);
-  assertEquals(0, focusPCount);
-  assertEquals(1, focusButtonCount);
-
-  // Passing in a list with no focusable elements returns false.
-  assertFalse(NodeUtil.setFocusToFirstFocusable([p]));
-
-  // Passing in an empty list returns false.
-  assertFalse(NodeUtil.setFocusToFirstFocusable([]));
-});
diff --git a/ui/accessibility/extensions/caretbrowsing/options.html b/ui/accessibility/extensions/caretbrowsing/options.html
deleted file mode 100644
index 0a93302..0000000
--- a/ui/accessibility/extensions/caretbrowsing/options.html
+++ /dev/null
@@ -1,160 +0,0 @@
-<html>
-<head>
-  <title>Caret Browsing Options</title>
-  <style>
-    body {
-      font-family: Lucida Grande, sans-serif, arial, helvetica;
-      width: 920px;
-      margin-left: auto;
-      margin-right: auto;
-    }
-    .banner {
-      width: 100%;
-      float: left;
-    }
-    .banner_left {
-      padding: 8px;
-      float: left;
-    }
-    .banner_right {
-      padding: 8px;
-    }
-    .body_wrapper {
-      width: 100%;
-      float: left;
-    }
-    .body_left {
-      border: 0;
-      padding: 0;
-      margin: 0;
-      width: 50%;
-      float: left;
-    }
-    .body_right {
-      border: 0;
-      padding: 0;
-      margin: 0;
-      width: 46%;
-      float: left;
-    }
-    .body_inner {
-      padding: 0 32px;
-    }
-    body.mac .nonmac {
-      display: none;
-    }
-    body.nonmac .mac {
-      display: none;
-    }
-    body.cros .noncros {
-      display: none;
-    }
-    body.noncros .cros {
-      display: none;
-    }
-    .key {
-      border: 1px solid #666;
-      color: #444;
-      padding: 0.2em 0.8em;
-      margin: 0 0.3em;
-      background: #eee;
-    }
-    p {
-      line-height: 1.6em;
-    }
-    fieldset {
-      margin-bottom: 1em;
-    }
-    fieldset div {
-      margin: 0.6em 0;
-    }
-    p.cros img {
-      vertical-align: middle;
-    }
-  </style>
-  <link href="caretbrowsing.css" rel="stylesheet" type="text/css">
-  <script src="axs_testing.js"></script>
-  <script src="traverse_util.js"></script>
-  <script src="storage.js"></script>
-  <script src="caretbrowsing.js"></script>
-  <script src="options.js"></script>
-</head>
-<body caretbrowsing="on">
-
-<div class="banner">
-  <div class="banner_left">
-    <img src="caret_128.png" class="logo" alt="">
-  </div>
-  <div class="banner_right">
-    <h1 i18n-content="caret_browsing_appName"></h1>
-    <p i18n-content="caret_browsing_subheading1"></p>
-    <p i18n-content="caret_browsing_subheading2"></p>
-  </div>
-</div>
-
-<div class="body_wrapper">
-  <div class="body_left">
-    <div class="body_inner">
-      <h2 i18n-content="caret_browsing_keyboardCommands"></h2>
-
-      <p class="noncros" i18n-content="caret_browsing_enableDisableNonCros"></p>
-      <p class="cros" i18n-content="caret_browsing_enableDisableCros"></p>
-
-      <div i18n-content="caret_browsing_navHelp"></div>
-
-      <p class="nonmac" i18n-content="caret_browsing_moveByWordsNonMac"></p>
-      <p class="mac" i18n-content="caret_browsing_moveByWordsMac"></p>
-
-      <div i18n-content="caret_browsing_focusHelp"></div>
-    </div>
-  </div>
-  <div class="body_right">
-    <div class="body_inner">
-      <h2></h2>
-
-      <fieldset>
-        <legend i18n-content="caret_browsing_whenEnabled"></legend>
-
-        <div>
-        <input type="radio" id="onenable_anim" name="onenable" value="anim">
-        <label for="onenable_anim" i18n-content="caret_browsing_animation"></label>
-        </div>
-
-        <div>
-        <input type="radio" id="onenable_flash" name="onenable" value="flash">
-        <label for="onenable_flash" i18n-content="caret_browsing_flash"></label>
-        </div>
-
-        <div>
-        <input type="radio" id="onenable_nothing" name="onenable" value="none">
-        <label for="onenable_nothing" i18n-content="caret_browsing_noFeedback"></label>
-        </div>
-
-      </fieldset>
-
-      <fieldset>
-        <legend i18n-content="caret_browsing_jump"></legend>
-
-        <div>
-        <input type="radio" id="onjump_anim" name="onjump" value="anim">
-        <label for="onjump_anim" i18n-content="caret_browsing_animation"></label>
-        </div>
-
-        <div>
-        <input type="radio" id="onjump_flash" name="onjump" value="flash">
-        <label for="onjump_flash" i18n-content="caret_browsing_flash"></label>
-        </div>
-
-        <div>
-        <input type="radio" id="onjump_nothing" name="onjump" value="none">
-        <label for="onjump_nothing" i18n-content="caret_browsing_noFeedback"></label>
-        </div>
-
-      </fieldset>
-
-    </div>
-  </div>
-</div>
-
-</body>
-</html>
diff --git a/ui/accessibility/extensions/caretbrowsing/options.js b/ui/accessibility/extensions/caretbrowsing/options.js
deleted file mode 100644
index 1a2a9e5..0000000
--- a/ui/accessibility/extensions/caretbrowsing/options.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/* Copyright 2014 The Chromium Authors
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file. */
-
-Storage.initialize();
-
-function setRadio(name, defaultValue) {
-  let value = Storage[name];
-  const controls = document.querySelectorAll(
-      'input[type="radio"][name="' + name + '"]');
-  for (let i = 0; i < controls.length; i++) {
-    const c = controls[i];
-    if (c.value == value) {
-      c.checked = true;
-    }
-    c.addEventListener('change', function(evt) {
-      if (evt.target.checked) {
-        Storage[evt.target.name] = evt.target.value;
-      }
-    }, false);
-  }
-}
-
-function load() {
-  const isMac = (navigator.appVersion.indexOf("Mac") != -1);
-  if (isMac) {
-    document.body.classList.add('mac');
-  } else {
-    document.body.classList.add('nonmac');
-  }
-
-  const isCros = (navigator.appVersion.indexOf("CrOS") != -1);
-  if (isCros) {
-    document.body.classList.add('cros');
-  } else {
-    document.body.classList.add('noncros');
-  }
-
-  setRadio('onenable', 'anim');
-  setRadio('onjump', 'flash');
-
-  const heading = document.querySelector('h1');
-  const sel = window.getSelection();
-  sel.setBaseAndExtent(heading, 0, heading, 0);
-
-  document.title =
-      chrome.i18n.getMessage('caret_browsing_caretBrowsingOptions');
-  const i18nElements = document.querySelectorAll('*[i18n-content]');
-  for (let i = 0; i < i18nElements.length; i++) {
-    const elem = i18nElements[i];
-    const msg = elem.getAttribute('i18n-content');
-    elem.innerHTML = chrome.i18n.getMessage(msg);
-  }
-}
-
-window.addEventListener('load', load, false);
diff --git a/ui/accessibility/extensions/caretbrowsing/selection_util.js b/ui/accessibility/extensions/caretbrowsing/selection_util.js
deleted file mode 100644
index b03e901..0000000
--- a/ui/accessibility/extensions/caretbrowsing/selection_util.js
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** A collection of functions for dealing with selections. */
-class SelectionUtil {
-  /**
-   * Get the rectangle for a cursor position. This is tricky because
-   * you can't get the bounding rectangle of an empty range, so this function
-   * computes the rect by trying a range including one character earlier or
-   * later than the cursor position.
-   * @param {Cursor} cursor A single cursor position.
-   * @return {{left: number, top: number, width: number, height: number}}
-   *     The bounding rectangle of the cursor.
-   */
-  static getCursorRect(cursor) {
-    let node = cursor.node;
-    const index = cursor.index;
-    const rect = {left: 0, top: 0, width: 1, height: 0};
-    if (node.constructor == Text) {
-      let left = index;
-      let right = index;
-      const max = node.data.length;
-      const newRange = document.createRange();
-      while (left > 0 || right < max) {
-        if (left > 0) {
-          left--;
-          newRange.setStart(node, left);
-          newRange.setEnd(node, index);
-          const rangeRect = newRange.getBoundingClientRect();
-          if (rangeRect && rangeRect.width && rangeRect.height) {
-            rect.left = rangeRect.right;
-            rect.top = rangeRect.top;
-            rect.height = rangeRect.height;
-            break;
-          }
-        }
-        if (right < max) {
-          right++;
-          newRange.setStart(node, index);
-          newRange.setEnd(node, right);
-          const rangeRect = newRange.getBoundingClientRect();
-          if (rangeRect && rangeRect.width && rangeRect.height) {
-            rect.left = rangeRect.left;
-            rect.top = rangeRect.top;
-            rect.height = rangeRect.height;
-            break;
-          }
-        }
-      }
-    } else {
-      rect.height = node.offsetHeight;
-      while (node !== null) {
-        rect.left += node.offsetLeft;
-        rect.top += node.offsetTop;
-        node = node.offsetParent;
-      }
-    }
-    rect.left += window.pageXOffset;
-    rect.top += window.pageYOffset;
-    return rect;
-  }
-
-  /**
-   * Return true if the selection directionality is ambiguous, which happens
-   * if, for example, the user double-clicks in the middle of a word to select
-   * it. In that case, the selection should extend by the right edge if the
-   * user presses right, and by the left edge if the user presses left.
-   * @param {Selection} sel The selection.
-   * @return {boolean} True if the selection directionality is ambiguous.
-   */
-  static isAmbiguous(sel) {
-    return (
-        sel.anchorNode != sel.baseNode || sel.anchorOffset != sel.baseOffset ||
-        sel.focusNode != sel.extentNode || sel.focusOffset != sel.extentOffset);
-  }
-
-  /**
-   * Create a Cursor from the anchor position of the selection, the
-   * part that doesn't normally move.
-   * @param {Selection} sel The selection.
-   * @return {Cursor} A cursor pointing to the selection's anchor location.
-   */
-  static makeAnchorCursor(sel) {
-    return new Cursor(
-        sel.anchorNode, sel.anchorOffset,
-        TraverseUtil.getNodeText(sel.anchorNode));
-  }
-
-  /**
-   * Create a Cursor from the focus position of the selection.
-   * @param {Selection} sel The selection.
-   * @return {Cursor} A cursor pointing to the selection's focus location.
-   */
-  static makeFocusCursor(sel) {
-    return new Cursor(
-        sel.focusNode, sel.focusOffset,
-        TraverseUtil.getNodeText(sel.focusNode));
-  }
-
-  /**
-   * Create a Cursor from the left boundary of the selection - the boundary
-   * closer to the start of the document.
-   * @param {Selection} sel The selection.
-   * @return {Cursor} A cursor pointing to the selection's left boundary.
-   */
-  static makeLeftCursor(sel) {
-    const range = sel.rangeCount == 1 ? sel.getRangeAt(0) : null;
-    if (range && range.endContainer == sel.anchorNode &&
-        range.endOffset == sel.anchorOffset) {
-      return SelectionUtil.makeFocusCursor(sel);
-    } else {
-      return SelectionUtil.makeAnchorCursor(sel);
-    }
-  }
-
-  /**
-   * Create a Cursor from the right boundary of the selection - the boundary
-   * closer to the end of the document.
-   * @param {Selection} sel The selection.
-   * @return {Cursor} A cursor pointing to the selection's right boundary.
-   */
-  static makeRightCursor(sel) {
-    const range = sel.rangeCount == 1 ? sel.getRangeAt(0) : null;
-    if (range && range.endContainer == sel.anchorNode &&
-        range.endOffset == sel.anchorOffset) {
-      return SelectionUtil.makeAnchorCursor(sel);
-    } else {
-      return SelectionUtil.makeFocusCursor(sel);
-    }
-  }
-
-  /**
-   * Try to set the window's selection to be between the given start and end
-   * cursors, and return whether or not it was successful.
-   * @param {Cursor} start The start position.
-   * @param {Cursor} end The end position.
-   * @return {boolean} True if the selection was successfully set.
-   */
-  static setAndValidateSelection(start, end) {
-    const sel = window.getSelection();
-    sel.setBaseAndExtent(start.node, start.index, end.node, end.index);
-
-    if (sel.rangeCount != 1) {
-      return false;
-    }
-
-    return (
-        sel.anchorNode == start.node && sel.anchorOffset == start.index &&
-        sel.focusNode == end.node && sel.focusOffset == end.index);
-  }
-
-  /**
-   * Note: the built-in function by the same name is unreliable.
-   * @param {Selection} sel The selection.
-   * @return {boolean} True if the start and end positions are the same.
-   */
-  static isCollapsed(sel) {
-    return (
-        sel.anchorOffset == sel.focusOffset && sel.anchorNode == sel.focusNode);
-  }
-}
diff --git a/ui/accessibility/extensions/caretbrowsing/selection_util_test.js b/ui/accessibility/extensions/caretbrowsing/selection_util_test.js
deleted file mode 100644
index 0a75ff01..0000000
--- a/ui/accessibility/extensions/caretbrowsing/selection_util_test.js
+++ /dev/null
@@ -1,169 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-GEN_INCLUDE(['selection_util.js', 'traverse_util.js']);
-
-GEN_INCLUDE(['../testing/webstore_extension_test_base.js']);
-
-/** Test fixture for selection_util.js. */
-SelectionUtilUnitTest = class extends WebstoreExtensionTest {};
-
-/**
- * @param {chrome.automation.Rect} expected
- * @param {chrome.automation.Rect} actual
- * @return {boolean}
- */
-function checkRect(expected, actual) {
-  assertEquals(expected.left, actual.left);
-  assertEquals(expected.top, actual.top);
-  assertEquals(expected.width, actual.width);
-  assertEquals(expected.height, actual.height);
-}
-
-/**
- * @param {Cursor} expected
- * @param {Cursor} actual
- * @return {boolean}
- */
-function checkCursor(expected, actual) {
-  assertEquals(expected.node, actual.node);
-  assertEquals(expected.index, actual.index);
-  assertEquals(expected.text, actual.text);
-}
-
-TEST_F('SelectionUtilUnitTest', 'GetCursorRect', function() {
-  const text = document.createTextNode('My Favorite Things');
-  const div = document.createElement('div');
-  div.appendChild(text);
-  document.body.appendChild(div);
-  div.style = 'position: absolute; left: 100px; top: 70px';
-
-  let cursor = new Cursor(text, 0, text.nodeValue);
-  let expected = {left: 100, top: 70, width: 1, height: 19};
-  let actual = SelectionUtil.getCursorRect(cursor);
-  checkRect(expected, actual);
-
-  cursor.index = 3;
-  expected = {left: 126, top: 70, width: 1, height: 19};
-  actual = SelectionUtil.getCursorRect(cursor);
-  checkRect(expected, actual);
-
-  const button = document.createElement('button');
-  button.textContent = 'Get soundtrack';
-  button.style = 'position: absolute; left: 10px; top: 0px';
-  document.body.appendChild(button);
-
-  cursor = new Cursor(button, 0, button.textContent);
-  expected = {left: 10, top: 0, width: 1, height: 22};
-  actual = SelectionUtil.getCursorRect(cursor);
-  checkRect(expected, actual);
-});
-
-TEST_F('SelectionUtilUnitTest', 'IsAmbiguous', function() {
-  const text =
-      document.createTextNode('Raindrops on roses and whiskers on kittens');
-  let selection = window.getSelection();
-  selection.setBaseAndExtent(text, 13, text, 31); /* roses and whiskers */
-  assertFalse(SelectionUtil.isAmbiguous(selection));
-
-  selection.setBaseAndExtent(text, 31, text, 13);
-  assertFalse(SelectionUtil.isAmbiguous(selection));
-
-  // Mock a situation where the selection is ambiguous.
-  selection = {
-    anchorNode: text,
-    baseNode: text,
-    anchorOffset: 13,
-    baseOffset: 31,
-    focusNode: text,
-    extentNode: text,
-    focusOffset: 31,
-    extentOffset: 13
-  };
-  assertTrue(SelectionUtil.isAmbiguous(selection));
-});
-
-TEST_F('SelectionUtilUnitTest', 'MakeCursor', function() {
-  const text =
-      document.createTextNode('Bright copper kettles and warm woolen mittens');
-  document.body.appendChild(text);
-
-  const selection = window.getSelection();
-  selection.setBaseAndExtent(text, 7, text, 21); /* copper kettles */
-
-  const leftCursor = new Cursor(text, 7, text.nodeValue);
-  const rightCursor = new Cursor(text, 21, text.nodeValue);
-
-  let actual = SelectionUtil.makeAnchorCursor(selection);
-  checkCursor(leftCursor, actual);
-
-  actual = SelectionUtil.makeFocusCursor(selection);
-  checkCursor(rightCursor, actual);
-
-  actual = SelectionUtil.makeLeftCursor(selection);
-  checkCursor(leftCursor, actual);
-
-  actual = SelectionUtil.makeRightCursor(selection);
-  checkCursor(rightCursor, actual);
-
-  // Reverse the focus and anchor.
-  selection.setBaseAndExtent(text, 21, text, 7);
-
-  actual = SelectionUtil.makeAnchorCursor(selection);
-  checkCursor(rightCursor, actual);
-
-  actual = SelectionUtil.makeFocusCursor(selection);
-  checkCursor(leftCursor, actual);
-
-  actual = SelectionUtil.makeLeftCursor(selection);
-  checkCursor(leftCursor, actual);
-
-  actual = SelectionUtil.makeRightCursor(selection);
-  checkCursor(rightCursor, actual);
-});
-
-TEST_F('SelectionUtilUnitTest', 'SetAndValidateSelection', function() {
-  const text =
-      document.createTextNode('Brown paper packages tied up with strings');
-  const start = new Cursor(text, 6, text.nodeValue);
-  let end = new Cursor(text, 20, text.nodeValue);
-
-  // Trying to set the selection to a node not in the document should return
-  // false.
-  assertFalse(SelectionUtil.setAndValidateSelection(start, end));
-
-  document.body.appendChild(text);
-  assertTrue(SelectionUtil.setAndValidateSelection(start, end));
-
-  // Validate that the selection was set properly.
-  const selection = window.getSelection();
-  assertEquals(text, selection.anchorNode);
-  assertEquals(6, selection.anchorOffset);
-  assertEquals(text, selection.focusNode);
-  assertEquals(20, selection.focusOffset);
-
-  // Check that a selection across nodes works properly.
-  const secondText =
-      document.createTextNode('These are a few of my favorite things');
-  document.body.appendChild(secondText);
-  end = new Cursor(secondText, 5, secondText.nodeValue);
-  assertTrue(SelectionUtil.setAndValidateSelection(start, end));
-
-  assertEquals(text, selection.anchorNode);
-  assertEquals(6, selection.anchorOffset);
-  assertEquals(secondText, selection.focusNode);
-  assertEquals(5, selection.focusOffset);
-});
-
-TEST_F('SelectionUtilUnitTest', 'IsCollapsed', function() {
-  const text = document.createTextNode('When the dog bites');
-  document.body.appendChild(text);
-
-  const selection = window.getSelection();
-  selection.setBaseAndExtent(text, 0, text, 0);
-  assertTrue(SelectionUtil.isCollapsed(selection));
-
-  selection.setBaseAndExtent(text, 0, text, 4);
-  assertFalse(SelectionUtil.isCollapsed(selection));
-});
diff --git a/ui/accessibility/extensions/caretbrowsing/storage.js b/ui/accessibility/extensions/caretbrowsing/storage.js
deleted file mode 100644
index 515a8482..0000000
--- a/ui/accessibility/extensions/caretbrowsing/storage.js
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/** @enum {string} */
-const FlourishType = {
-  ANIMATE: 'anim',
-  FLASH: 'flash',
-  NONE: 'none',
-};
-
-/**
- * Class to handle interactions with the chrome.storage API and values that are
- * stored that way.
- */
-class Storage {
-  /**
-   * @param {function()=} opt_callbackForTesting
-   * @private
-   */
-  constructor(opt_callbackForTesting) {
-    /** @private {boolean} */
-    this.enabled_ = Storage.ENABLED.defaultValue;
-    /** @private {!FlourishType} */
-    this.onEnable_ = Storage.ON_ENABLE.defaultValue;
-    /** @private {!FlourishType} */
-    this.onJump_ = Storage.ON_JUMP.defaultValue;
-
-    this.init_(opt_callbackForTesting);
-  }
-
-  // ======= Public Methods =======
-
-  static initialize() {
-    if (!Storage.instance) {
-      Storage.instance = new Storage();
-    }
-  }
-
-  /** @return {boolean} */
-  static get enabled() { return Storage.instance.enabled_; }
-  /** @return {!FlourishType} */
-  static get onEnable() { return Storage.instance.onEnable_; }
-  /** @return {!FlourishType} */
-  static get onJump() { return Storage.instance.onJump_; }
-
-  /** @param {boolean} newValue */
-  static set enabled(newValue) {
-    Storage.instance.setOrResetValue_(Storage.ENABLED, newValue);
-    Storage.instance.store_(Storage.ENABLED);
-  }
-
-  /** @param {!FlourishType} newBehavior */
-  static set onEnable(newBehavior) {
-    Storage.instance.setOrResetValue_(Storage.ON_ENABLE, newBehavior);
-    Storage.instance.store_(Storage.ON_ENABLE);
-  }
-
-  /** @param {!FlourishType} newBehavior */
-  static set onJump(newBehavior) {
-    Storage.instance.setOrResetValue_(Storage.ON_JUMP, newBehavior);
-    Storage.instance.store_(Storage.ON_JUMP);
-  }
-
-  // ======= Private Methods =======
-
-  /**
-   * @param {!Storage.Value} container
-   * @param {*} newValue
-   * @private
-   */
-  setOrResetValue_(container, newValue) {
-    if (newValue === container.get()) {
-      return;
-    }
-
-    if (container.validate(newValue)) {
-      container.set(newValue);
-    } else {
-      container.reset();
-    }
-
-    container.listeners.forEach(listener => listener(newValue));
-  }
-
-  /**
-   * @param {function()=} opt_callback
-   * @private
-   */
-  init_(opt_callback) {
-    chrome.storage.onChanged.addListener(this.onChange_);
-    chrome.storage.local.get(null /* all values */, (results) => {
-      const storedValues = Storage.ALL_VALUES.filter(v => results[v.key]);
-      for (const value of storedValues) {
-        this.setOrResetValue_(value, results[value.key]);
-      }
-      opt_callback ? opt_callback() : undefined;
-    });
-  }
-
-  /**
-   * @param {!Object<string, chrome.storage.StorageChange>} changes
-   * @private
-   */
-  onChange_(changes) {
-    const changedValues = Storage.ALL_VALUES.filter(v => changes[v.key]);
-    for (const value of changedValues) {
-      Storage.instance.setOrResetValue_(value, changes[value.key].newValue);
-    }
-  }
-
-  /**
-   * @param {!Storage.Value} value
-   * @private
-   */
-  store_(value) {
-    chrome.storage.local.set({ [value.key]: value.get() });
-  }
-
-  // ======= Stored Values =======
-
-  /**
-   * @typedef {{
-   *     key: string,
-   *     defaultValue: *,
-   *     validate: function(*): boolean,
-   *     get: function: *,
-   *     set: function(*),
-   *     reset: function(),
-   *     listeners: !Array<function(*)>
-   * }}
-   */
-  static Value;
-
-  /** @const {!Storage.Value} */
-  static ENABLED = {
-    key: 'enabled',
-    defaultValue: false,
-    validate: (enabled) => enabled === true || enabled === false,
-    get: () => Storage.instance.enabled_,
-    set: (enabled) => Storage.instance.enabled_ = enabled,
-    reset: () => Storage.instance.enabled_ = Storage.ENABLED.defaultValue,
-    listeners: [],
-  };
-
-  /** @const {!Storage.Value} */
-  static ON_ENABLE = {
-    key: 'onenable',
-    defaultValue: FlourishType.ANIMATE,
-    validate: (onEnable) => Object.values(FlourishType).includes(onEnable),
-    get: () => Storage.instance.onEnable_,
-    set: (onEnable) => Storage.instance.onEnable_ = onEnable,
-    reset: () => Storage.instance.onEnable_ = Storage.ON_ENABLE.defaultValue,
-    listeners: [],
-  };
-
-  /** @const {!Storage.Value} */
-  static ON_JUMP = {
-    key: 'onjump',
-    defaultValue: FlourishType.FLASH,
-    validate: (onJump) => Object.values(FlourishType).includes(onJump),
-    get: () => Storage.instance.onJump_,
-    set: (onJump) => Storage.instance.onJump_ = onJump,
-    reset: () => Storage.instance.onJump_ = Storage.ON_JUMP.defaultValue,
-    listeners: [],
-  };
-
-  /** @const {!Array<!Storage.Value>} */
-  static ALL_VALUES = [
-      Storage.ENABLED, Storage.ON_ENABLE, Storage.ON_JUMP,
-  ];
-}
diff --git a/ui/accessibility/extensions/caretbrowsing/storage_test.js b/ui/accessibility/extensions/caretbrowsing/storage_test.js
deleted file mode 100644
index 9dc7e06..0000000
--- a/ui/accessibility/extensions/caretbrowsing/storage_test.js
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-GEN_INCLUDE(['storage.js']);
-
-GEN_INCLUDE([
-    '../webstore_extension_test_base.js',
-    '//chrome/browser/resources/chromeos/accessibility/common/testing/' +
-        'callback_helper.js',
-    '//chrome/browser/resources/chromeos/accessibility/common/testing/' +
-        'mock_storage.js',
-]);
-
-/** Test fixture for storage.js. */
-CaretBrowsingStorageTest = class extends WebstoreExtensionTest {
-  /** @override */
-  setUp() {
-    this.callbackHelper_ = new CallbackHelper(this);
-    chrome.storage = MockStorage;
-    Storage.initialize();
-  }
-
-  /**
-   * Increments a counter, to wait for all callbacks to be completed before
-   * finishing the test.
-   * @param {Function=} opt_callback
-   * @return {Function}
-   */
-  newCallback(opt_callback) {
-    return this.callbackHelper_.wrap(opt_callback);
-  }
-};
-
-TEST_F('CaretBrowsingStorageTest', 'DefaultValues', function() {
-  assertEquals(false, Storage.enabled);
-  assertEquals(FlourishType.ANIMATE, Storage.onEnable);
-  assertEquals(FlourishType.FLASH, Storage.onJump);
-});
-
-TEST_F('CaretBrowsingStorageTest', 'SetValues', function() {
-  // enabled
-  Storage.enabled = true;
-  assertEquals(true, Storage.enabled);
-  let storedValue = MockStorage.local_[Storage.ENABLED.key];
-  assertEquals('boolean', typeof (storedValue));
-  assertEquals(true, storedValue);
-
-  // onEnable
-  Storage.onEnable = FlourishType.NONE;
-  assertEquals(FlourishType.NONE, Storage.onEnable);
-  storedValue = MockStorage.local_[Storage.ON_ENABLE.key];
-  assertTrue(Object.values(FlourishType).includes(storedValue));
-  assertEquals(FlourishType.NONE, storedValue);
-
-  // onJump
-  Storage.onJump = FlourishType.ANIMATE;
-  assertEquals(FlourishType.ANIMATE, Storage.onJump);
-  storedValue = MockStorage.local_[Storage.ON_JUMP.key];
-  assertTrue(Object.values(FlourishType).includes(storedValue));
-  assertEquals(FlourishType.ANIMATE, storedValue);
-});
-
-TEST_F('CaretBrowsingStorageTest', 'SetInvalidValues', function() {
-  // enabled
-  Storage.enabled = 7;  // enabled must be a boolean
-  assertEquals(false, Storage.enabled);
-  storedValue = MockStorage.local_[Storage.ENABLED.key];
-  assertEquals(false, storedValue);
-
-  // onEnable
-  Storage.onEnable = true;  // onEnable must be a FlourishType.
-  assertEquals(FlourishType.ANIMATE, Storage.onEnable);
-  storedValue = MockStorage.local_[Storage.ON_ENABLE.key];
-  assertTrue(Object.values(FlourishType).includes(storedValue));
-  assertEquals(FlourishType.ANIMATE, storedValue);
-
-  // onJump
-  Storage.onJump = 'x';  // onJump must be a FlourishType.
-  assertEquals(FlourishType.FLASH, Storage.onJump);
-  storedValue = MockStorage.local_[Storage.ON_JUMP.key];
-  assertTrue(Object.values(FlourishType).includes(storedValue));
-  assertEquals(FlourishType.FLASH, storedValue);
-});
-
-TEST_F('CaretBrowsingStorageTest', 'Listeners', function() {
-  Storage.ENABLED.listeners.push(this.newCallback(newVal => {
-    assertEquals(true, newVal);
-    Storage.ENABLED.listeners.pop();
-  }));
-  Storage.enabled = true;
-
-  Storage.ON_ENABLE.listeners.push(this.newCallback(newVal => {
-    assertEquals(FlourishType.NONE, newVal);
-    Storage.ON_ENABLE.listeners.pop();
-  }));
-  Storage.onEnable = FlourishType.NONE;
-
-  Storage.ON_JUMP.listeners.push(this.newCallback(newVal => {
-    assertEquals(FlourishType.ANIMATE, newVal);
-    Storage.ON_JUMP.listeners.pop();
-  }));
-  Storage.onJump = FlourishType.ANIMATE;
-
-});
-
-TEST_F('CaretBrowsingStorageTest', 'InitialFetch', function() {
-  // Make sure any values from previous tests are cleared.
-  MockStorage.local_ = {};
-
-  Storage.enabled = true;
-  Storage.onJump = FlourishType.NONE;
-
-  // Simulate re-starting the extension by creating a new instance.
-  Storage.instance = new Storage(this.newCallback(() => {
-    assertEquals(FlourishType.NONE, Storage.onJump);
-    assertEquals(true, Storage.enabled);
-
-    // Check that unset values are at default.
-    assertEquals(FlourishType.ANIMATE, Storage.onEnable);
-  }));
-});
-
-TEST_F('CaretBrowsingStorageTest', 'OnChange', function() {
-  Storage.ON_ENABLE.listeners.push(this.newCallback((newVal) => {
-    assertEquals(FlourishType.NONE, newVal);
-    Storage.ON_ENABLE.listeners.pop();
-  }));
-
-
-  MockStorage.callOnChangedListeners({
-    [Storage.ON_ENABLE.key]: FlourishType.NONE });
-
-  // Check that the value was set properly, in addition to the callbacks being
-  // called.
-  assertEquals(FlourishType.NONE, Storage.onEnable);
-});
diff --git a/ui/accessibility/extensions/caretbrowsing/traverse_util.js b/ui/accessibility/extensions/caretbrowsing/traverse_util.js
deleted file mode 100644
index 1485be8..0000000
--- a/ui/accessibility/extensions/caretbrowsing/traverse_util.js
+++ /dev/null
@@ -1,869 +0,0 @@
-/* Copyright 2014 The Chromium Authors
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file. */
-
-/**
- * @fileoverview Low-level DOM traversal utility functions to find the
- *     next (or previous) character, word, sentence, line, or paragraph,
- *     in a completely stateless manner without actually manipulating the
- *     selection.
- */
-
-/**
- * A class to represent a cursor location in the document,
- * like the start position or end position of a selection range.
- *
- * Later this may be extended to support "virtual text" for an object,
- * like the ALT text for an image.
- *
- * Note: we cache the text of a particular node at the time we
- * traverse into it. Later we should add support for dynamically
- * reloading it.
- * @param {Node} node The DOM node.
- * @param {number} index The index of the character within the node.
- * @param {string} text The cached text contents of the node.
- * @constructor
- */
-Cursor = function(node, index, text) {
-  this.node = node;
-  this.index = index;
-  this.text = text;
-};
-
-/**
- * @return {Cursor} A new cursor pointing to the same location.
- */
-Cursor.prototype.clone = function() {
-  return new Cursor(this.node, this.index, this.text);
-};
-
-/**
- * Modify this cursor to point to the location that another cursor points to.
- * @param {Cursor} otherCursor The cursor to copy from.
- */
-Cursor.prototype.copyFrom = function(otherCursor) {
-  this.node = otherCursor.node;
-  this.index = otherCursor.index;
-  this.text = otherCursor.text;
-};
-
-/**
- * Utility functions for stateless DOM traversal.
- * @constructor
- */
-TraverseUtil = function() {};
-
-/**
- * Gets the text representation of a node. This allows us to substitute
- * alt text, names, or titles for html elements that provide them.
- * @param {Node} node A DOM node.
- * @return {string} A text string representation of the node.
- */
-TraverseUtil.getNodeText = function(node) {
-  if (node.constructor == Text) {
-    return node.data;
-  } else {
-    return '';
-  }
-};
-
-/**
- * Return true if a node should be treated as a leaf node, because
- * its children are properties of the object that shouldn't be traversed.
- *
- * TODO(dmazzoni): replace this with a predicate that detects nodes with
- * ARIA roles and other objects that have their own description.
- * For now we just detect a couple of common cases.
- *
- * @param {Node} node A DOM node.
- * @return {boolean} True if the node should be treated as a leaf node.
- */
-TraverseUtil.treatAsLeafNode = function(node) {
-  return node.childNodes.length == 0 ||
-         node.nodeName == 'SELECT' ||
-         node.nodeName == 'OBJECT';
-};
-
-/**
- * Return true only if a single character is whitespace.
- * From https://developer.mozilla.org/en/Whitespace_in_the_DOM,
- * whitespace is defined as one of the characters
- *  "\t" TAB \u0009
- *  "\n" LF  \u000A
- *  "\r" CR  \u000D
- *  " "  SPC \u0020.
- *
- * @param {string} c A string containing a single character.
- * @return {boolean} True if the character is whitespace, otherwise false.
- */
-TraverseUtil.isWhitespace = function(c) {
-  return (c == ' ' || c == '\n' || c == '\r' || c == '\t');
-};
-
-/**
- * Set the selection to the range between the given start and end cursors.
- * @param {Cursor} start The desired start of the selection.
- * @param {Cursor} end The desired end of the selection.
- * @return {Selection} the selection object.
- */
-TraverseUtil.setSelection = function(start, end) {
-  const sel = window.getSelection();
-  sel.removeAllRanges();
-  const range = document.createRange();
-  range.setStart(start.node, start.index);
-  range.setEnd(end.node, end.index);
-  sel.addRange(range);
-
-  return sel;
-};
-
-/**
- * Use the computed CSS style to figure out if this DOM node is currently
- * visible.
- * @param {Node} node A HTML DOM node.
- * @return {boolean} Whether or not the html node is visible.
- */
-TraverseUtil.isVisible = function(node) {
-  if (!node.style)
-    return true;
-  const style = window.getComputedStyle(/** @type {Element} */(node), null);
-  return (!!style && style.display != 'none' && style.visibility != 'hidden');
-};
-
-/**
- * Use the class name to figure out if this DOM node should be traversed.
- * @param {Node} node A HTML DOM node.
- * @return {boolean} Whether or not the html node should be traversed.
- */
-TraverseUtil.isSkipped = function(node) {
-  if (node.constructor == Text)
-    node = node.parentElement;
-  if (node.className == 'CaretBrowsing_Caret' ||
-      node.className == 'CaretBrowsing_AnimateCaret') {
-    return true;
-  }
-  return false;
-};
-
-/**
- * Moves the cursor forwards until it has crossed exactly one character.
- * @param {Cursor} cursor The cursor location where the search should start.
- *     On exit, the cursor will be immediately to the right of the
- *     character returned.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @return {?string} The character found, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.forwardsChar = function(cursor, nodesCrossed) {
-  while (true) {
-    // Move down until we get to a leaf node.
-    let childNode = null;
-    if (!TraverseUtil.treatAsLeafNode(cursor.node)) {
-      for (let i = cursor.index; i < cursor.node.childNodes.length; i++) {
-        const node = cursor.node.childNodes[i];
-        if (TraverseUtil.isSkipped(node)) {
-          nodesCrossed.push(node);
-          continue;
-        }
-        if (TraverseUtil.isVisible(node)) {
-          childNode = node;
-          break;
-        }
-      }
-    }
-    if (childNode) {
-      cursor.node = childNode;
-      cursor.index = 0;
-      cursor.text = TraverseUtil.getNodeText(cursor.node);
-      if (cursor.node.constructor != Text) {
-        nodesCrossed.push(cursor.node);
-      }
-      continue;
-    }
-
-    // Return the next character from this leaf node.
-    if (cursor.index < cursor.text.length)
-      return cursor.text[cursor.index++];
-
-    // Move to the next sibling, going up the tree as necessary.
-    while (cursor.node != null) {
-      // Try to move to the next sibling.
-      let siblingNode = null;
-      for (let node = cursor.node.nextSibling;
-           node != null;
-           node = node.nextSibling) {
-        if (TraverseUtil.isSkipped(node)) {
-          nodesCrossed.push(node);
-          continue;
-        }
-        if (TraverseUtil.isVisible(node)) {
-          siblingNode = node;
-          break;
-        }
-      }
-      if (siblingNode) {
-        cursor.node = siblingNode;
-        cursor.text = TraverseUtil.getNodeText(siblingNode);
-        cursor.index = 0;
-
-        if (cursor.node.constructor != Text) {
-          nodesCrossed.push(cursor.node);
-        }
-
-        break;
-      }
-
-      // Otherwise, move to the parent.
-      if (cursor.node.parentNode &&
-          cursor.node.parentNode.constructor != HTMLBodyElement) {
-        cursor.node = cursor.node.parentNode;
-        cursor.text = null;
-        cursor.index = 0;
-      } else {
-        return null;
-      }
-    }
-  }
-};
-
-/**
- * Moves the cursor backwards until it has crossed exactly one character.
- * @param {Cursor} cursor The cursor location where the search should start.
- *     On exit, the cursor will be immediately to the left of the
- *     character returned.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @return {?string} The previous character, or null if the top of the
- *     document has been reached.
- */
-TraverseUtil.backwardsChar = function(cursor, nodesCrossed) {
-  while (true) {
-    // Move down until we get to a leaf node.
-    let childNode = null;
-    if (!TraverseUtil.treatAsLeafNode(cursor.node)) {
-      for (let i = cursor.index - 1; i >= 0; i--) {
-        const node = cursor.node.childNodes[i];
-        if (TraverseUtil.isSkipped(node)) {
-          nodesCrossed.push(node);
-          continue;
-        }
-        if (TraverseUtil.isVisible(node)) {
-          childNode = node;
-          break;
-        }
-      }
-    }
-    if (childNode) {
-      cursor.node = childNode;
-      cursor.text = TraverseUtil.getNodeText(cursor.node);
-      if (cursor.text.length)
-        cursor.index = cursor.text.length;
-      else
-        cursor.index = cursor.node.childNodes.length;
-      if (cursor.node.constructor != Text)
-        nodesCrossed.push(cursor.node);
-      continue;
-    }
-
-    // Return the previous character from this leaf node.
-    if (cursor.text.length > 0 && cursor.index > 0) {
-      return cursor.text[--cursor.index];
-    }
-
-    // Move to the previous sibling, going up the tree as necessary.
-    while (true) {
-      // Try to move to the previous sibling.
-      let siblingNode = null;
-      for (let node = cursor.node.previousSibling;
-           node != null;
-           node = node.previousSibling) {
-        if (TraverseUtil.isSkipped(node)) {
-          nodesCrossed.push(node);
-          continue;
-        }
-        if (TraverseUtil.isVisible(node)) {
-          siblingNode = node;
-          break;
-        }
-      }
-      if (siblingNode) {
-        cursor.node = siblingNode;
-        cursor.text = TraverseUtil.getNodeText(siblingNode);
-        if (cursor.text.length)
-          cursor.index = cursor.text.length;
-        else
-          cursor.index = cursor.node.childNodes.length;
-        if (cursor.node.constructor != Text)
-          nodesCrossed.push(cursor.node);
-        break;
-      }
-
-      // Otherwise, move to the parent.
-      if (cursor.node.parentNode &&
-          cursor.node.parentNode.constructor != HTMLBodyElement) {
-        cursor.node = cursor.node.parentNode;
-        cursor.text = null;
-        cursor.index = 0;
-      } else {
-        return null;
-      }
-    }
-  }
-};
-
-/**
- * Finds the next character, starting from endCursor.  Upon exit, startCursor
- * and endCursor will surround the next character. If skipWhitespace is
- * true, will skip until a real character is found. Otherwise, it will
- * attempt to select all of the whitespace between the initial position
- * of endCursor and the next non-whitespace character.
- * @param {Cursor} startCursor On exit, points to the position before
- *     the char.
- * @param {Cursor} endCursor The position to start searching for the next
- *     char.  On exit, will point to the position past the char.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @param {boolean} skipWhitespace If true, will keep scanning until a
- *     non-whitespace character is found.
- * @return {?string} The next char, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getNextChar = function(
-    startCursor, endCursor, nodesCrossed, skipWhitespace) {
-
-  // Save the starting position and get the first character.
-  startCursor.copyFrom(endCursor);
-  let c = TraverseUtil.forwardsChar(endCursor, nodesCrossed);
-  if (c == null)
-    return null;
-
-  // Keep track of whether the first character was whitespace.
-  const initialWhitespace = TraverseUtil.isWhitespace(c);
-
-  // Keep scanning until we find a non-whitespace or non-skipped character.
-  while ((TraverseUtil.isWhitespace(c)) ||
-      (TraverseUtil.isSkipped(endCursor.node))) {
-    c = TraverseUtil.forwardsChar(endCursor, nodesCrossed);
-    if (c == null)
-      return null;
-  }
-  if (skipWhitespace || !initialWhitespace) {
-    // If skipWhitepace is true, or if the first character we encountered
-    // was not whitespace, return that non-whitespace character.
-    startCursor.copyFrom(endCursor);
-    startCursor.index--;
-    return c;
-  }
-  else {
-    for (let i = 0; i < nodesCrossed.length; i++) {
-      if (TraverseUtil.isSkipped(nodesCrossed[i])) {
-        // We need to make sure that startCursor and endCursor aren't
-        // surrounding a skippable node.
-        endCursor.index--;
-        startCursor.copyFrom(endCursor);
-        startCursor.index--;
-        return ' ';
-      }
-    }
-    // Otherwise, return all of the whitespace before that last character.
-    endCursor.index--;
-    return ' ';
-  }
-};
-
-/**
- * Finds the previous character, starting from startCursor.  Upon exit,
- * startCursor and endCursor will surround the previous character.
- * If skipWhitespace is true, will skip until a real character is found.
- * Otherwise, it will attempt to select all of the whitespace between
- * the initial position of endCursor and the next non-whitespace character.
- * @param {Cursor} startCursor The position to start searching for the
- *     char. On exit, will point to the position before the char.
- * @param {Cursor} endCursor The position to start searching for the next
- *     char. On exit, will point to the position past the char.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @param {boolean} skipWhitespace If true, will keep scanning until a
- *     non-whitespace character is found.
- * @return {?string} The previous char, or null if the top of the
- *     document has been reached.
- */
-TraverseUtil.getPreviousChar = function(
-    startCursor, endCursor, nodesCrossed, skipWhitespace) {
-
-  // Save the starting position and get the first character.
-  endCursor.copyFrom(startCursor);
-  let c = TraverseUtil.backwardsChar(startCursor, nodesCrossed);
-  if (c == null)
-    return null;
-
-  // Keep track of whether the first character was whitespace.
-  const initialWhitespace = TraverseUtil.isWhitespace(c);
-
-  // Keep scanning until we find a non-whitespace or non-skipped character.
-  while ((TraverseUtil.isWhitespace(c)) ||
-      (TraverseUtil.isSkipped(startCursor.node))) {
-    c = TraverseUtil.backwardsChar(startCursor, nodesCrossed);
-    if (c == null)
-      return null;
-  }
-  if (skipWhitespace || !initialWhitespace) {
-    // If skipWhitepace is true, or if the first character we encountered
-    // was not whitespace, return that non-whitespace character.
-    endCursor.copyFrom(startCursor);
-    endCursor.index++;
-    return c;
-  } else {
-    for (let i = 0; i < nodesCrossed.length; i++) {
-      if (TraverseUtil.isSkipped(nodesCrossed[i])) {
-        startCursor.index++;
-        endCursor.copyFrom(startCursor);
-        endCursor.index++;
-        return ' ';
-      }
-    }
-    // Otherwise, return all of the whitespace before that last character.
-    startCursor.index++;
-    return ' ';
-  }
-};
-
-/**
- * Finds the next word, starting from endCursor.  Upon exit, startCursor
- * and endCursor will surround the next word.  A word is defined to be
- * a string of 1 or more non-whitespace characters in the same DOM node.
- * @param {Cursor} startCursor On exit, will point to the beginning of the
- *     word returned.
- * @param {Cursor} endCursor The position to start searching for the next
- *     word.  On exit, will point to the end of the word returned.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @return {?string} The next word, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getNextWord = function(startCursor, endCursor,
-    nodesCrossed) {
-
-  // Find the first non-whitespace or non-skipped character.
-  const cursor = endCursor.clone();
-  let c = TraverseUtil.forwardsChar(cursor, nodesCrossed);
-  if (c == null)
-    return null;
-  while ((TraverseUtil.isWhitespace(c)) ||
-      (TraverseUtil.isSkipped(cursor.node))) {
-    c = TraverseUtil.forwardsChar(cursor, nodesCrossed);
-    if (c == null)
-      return null;
-  }
-
-  // Set startCursor to the position immediately before the first
-  // character in our word. It's safe to decrement |index| because
-  // forwardsChar guarantees that the cursor will be immediately to the
-  // right of the returned character on exit.
-  startCursor.copyFrom(cursor);
-  startCursor.index--;
-
-  // Keep building up our word until we reach a whitespace character or
-  // would cross a tag.  Don't actually return any tags crossed, because this
-  // word goes up until the tag boundary but not past it.
-  endCursor.copyFrom(cursor);
-  let word = c;
-  const newNodesCrossed = [];
-  c = TraverseUtil.forwardsChar(cursor, newNodesCrossed);
-  if (c == null) {
-    return word;
-  }
-  while (!TraverseUtil.isWhitespace(c) &&
-     newNodesCrossed.length == 0) {
-    word += c;
-    endCursor.copyFrom(cursor);
-    c = TraverseUtil.forwardsChar(cursor, newNodesCrossed);
-    if (c == null) {
-      return word;
-    }
-  }
-  return word;
-};
-
-/**
- * Finds the previous word, starting from startCursor.  Upon exit, startCursor
- * and endCursor will surround the previous word.  A word is defined to be
- * a string of 1 or more non-whitespace characters in the same DOM node.
- * @param {Cursor} startCursor The position to start searching for the
- *     previous word.  On exit, will point to the beginning of the
- *     word returned.
- * @param {Cursor} endCursor On exit, will point to the end of the
- *     word returned.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @return {?string} The previous word, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getPreviousWord = function(startCursor, endCursor,
-    nodesCrossed) {
-  // Find the first non-whitespace or non-skipped character.
-  const cursor = startCursor.clone();
-  let c = TraverseUtil.backwardsChar(cursor, nodesCrossed);
-  if (c == null)
-    return null;
-  while ((TraverseUtil.isWhitespace(c) ||
-      (TraverseUtil.isSkipped(cursor.node)))) {
-    c = TraverseUtil.backwardsChar(cursor, nodesCrossed);
-    if (c == null)
-      return null;
-  }
-
-  // Set endCursor to the position immediately after the first
-  // character we've found (the last character of the word, since we're
-  // searching backwards).
-  endCursor.copyFrom(cursor);
-  endCursor.index++;
-
-  // Keep building up our word until we reach a whitespace character or
-  // would cross a tag.  Don't actually return any tags crossed, because this
-  // word goes up until the tag boundary but not past it.
-  startCursor.copyFrom(cursor);
-  let word = c;
-  const newNodesCrossed = [];
-  c = TraverseUtil.backwardsChar(cursor, newNodesCrossed);
-  if (c == null)
-    return word;
-  while (!TraverseUtil.isWhitespace(c) &&
-      newNodesCrossed.length == 0) {
-    word = c + word;
-    startCursor.copyFrom(cursor);
-    c = TraverseUtil.backwardsChar(cursor, newNodesCrossed);
-    if (c == null)
-      return word;
-  }
-
-  return word;
-};
-
-/**
- * Finds the next sentence, starting from endCursor.  Upon exit,
- * startCursor and endCursor will surround the next sentence.
- *
- * @param {Cursor} startCursor On exit, marks the beginning of the sentence.
- * @param {Cursor} endCursor The position to start searching for the next
- *     sentence.  On exit, will point to the end of the returned string.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @param {Object} breakTags Associative array of tags that should break
- *     the sentence.
- * @return {?string} The next sentence, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getNextSentence = function(
-    startCursor, endCursor, nodesCrossed, breakTags) {
-  return TraverseUtil.getNextString(
-      startCursor, endCursor, nodesCrossed,
-      function(str, word, nodes) {
-        if (str.substr(-1) == '.')
-          return true;
-        for (let i = 0; i < nodes.length; i++) {
-          if (TraverseUtil.isSkipped(nodes[i])) {
-            return true;
-          }
-          const style = window.getComputedStyle(nodes[i], null);
-          if (style && (style.display != 'inline' ||
-                        breakTags[nodes[i].tagName])) {
-            return true;
-          }
-        }
-        return false;
-      });
-};
-
-/**
- * Finds the previous sentence, starting from startCursor.  Upon exit,
- * startCursor and endCursor will surround the previous sentence.
- *
- * @param {Cursor} startCursor The position to start searching for the next
- *     sentence.  On exit, will point to the start of the returned string.
- * @param {Cursor} endCursor On exit, the end of the returned string.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @param {Object} breakTags Associative array of tags that should break
- *     the sentence.
- * @return {?string} The previous sentence, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getPreviousSentence = function(
-    startCursor, endCursor, nodesCrossed, breakTags) {
-  return TraverseUtil.getPreviousString(
-      startCursor, endCursor, nodesCrossed,
-      function(str, word, nodes) {
-        if (word.substr(-1) == '.')
-          return true;
-        for (let i = 0; i < nodes.length; i++) {
-          if (TraverseUtil.isSkipped(nodes[i])) {
-            return true;
-          }
-          const style = window.getComputedStyle(nodes[i], null);
-          if (style && (style.display != 'inline' ||
-                        breakTags[nodes[i].tagName])) {
-            return true;
-          }
-        }
-        return false;
-      });
-};
-
-/**
- * Finds the next line, starting from endCursor.  Upon exit,
- * startCursor and endCursor will surround the next line.
- *
- * @param {Cursor} startCursor On exit, marks the beginning of the line.
- * @param {Cursor} endCursor The position to start searching for the next
- *     line.  On exit, will point to the end of the returned string.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @param {number} lineLength The maximum number of characters in a line.
- * @param {Object} breakTags Associative array of tags that should break
- *     the line.
- * @return {?string} The next line, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getNextLine = function(
-    startCursor, endCursor, nodesCrossed, lineLength, breakTags) {
-  return TraverseUtil.getNextString(
-      startCursor, endCursor, nodesCrossed,
-      function(str, word, nodes) {
-        if (str.length + word.length + 1 > lineLength)
-          return true;
-        for (let i = 0; i < nodes.length; i++) {
-          if (TraverseUtil.isSkipped(nodes[i])) {
-            return true;
-          }
-          const style = window.getComputedStyle(nodes[i], null);
-          if (style && (style.display != 'inline' ||
-                        breakTags[nodes[i].tagName])) {
-            return true;
-          }
-        }
-        return false;
-      });
-};
-
-/**
- * Finds the previous line, starting from startCursor.  Upon exit,
- * startCursor and endCursor will surround the previous line.
- *
- * @param {Cursor} startCursor The position to start searching for the next
- *     line.  On exit, will point to the start of the returned string.
- * @param {Cursor} endCursor On exit, the end of the returned string.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @param {number} lineLength The maximum number of characters in a line.
- * @param {Object} breakTags Associative array of tags that should break
- *     the sentence.
- *  @return {?string} The previous line, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getPreviousLine = function(
-    startCursor, endCursor, nodesCrossed, lineLength, breakTags) {
-  return TraverseUtil.getPreviousString(
-      startCursor, endCursor, nodesCrossed,
-      function(str, word, nodes) {
-        if (str.length + word.length + 1 > lineLength)
-          return true;
-        for (let i = 0; i < nodes.length; i++) {
-          if (TraverseUtil.isSkipped(nodes[i])) {
-            return true;
-          }
-          const style = window.getComputedStyle(nodes[i], null);
-          if (style && (style.display != 'inline' ||
-                        breakTags[nodes[i].tagName])) {
-            return true;
-          }
-        }
-        return false;
-      });
-};
-
-/**
- * Finds the next paragraph, starting from endCursor.  Upon exit,
- * startCursor and endCursor will surround the next paragraph.
- *
- * @param {Cursor} startCursor On exit, marks the beginning of the paragraph.
- * @param {Cursor} endCursor The position to start searching for the next
- *     paragraph.  On exit, will point to the end of the returned string.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @return {?string} The next paragraph, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getNextParagraph = function(startCursor, endCursor,
-    nodesCrossed) {
-  return TraverseUtil.getNextString(
-      startCursor, endCursor, nodesCrossed,
-      function(str, word, nodes) {
-        for (let i = 0; i < nodes.length; i++) {
-          if (TraverseUtil.isSkipped(nodes[i])) {
-            return true;
-          }
-          const style = window.getComputedStyle(nodes[i], null);
-          if (style && style.display != 'inline') {
-            return true;
-          }
-        }
-        return false;
-      });
-};
-
-/**
- * Finds the previous paragraph, starting from startCursor.  Upon exit,
- * startCursor and endCursor will surround the previous paragraph.
- *
- * @param {Cursor} startCursor The position to start searching for the next
- *     paragraph.  On exit, will point to the start of the returned string.
- * @param {Cursor} endCursor On exit, the end of the returned string.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @return {?string} The previous paragraph, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getPreviousParagraph = function(
-    startCursor, endCursor, nodesCrossed) {
-  return TraverseUtil.getPreviousString(
-      startCursor, endCursor, nodesCrossed,
-      function(str, word, nodes) {
-        for (let i = 0; i < nodes.length; i++) {
-          if (TraverseUtil.isSkipped(nodes[i])) {
-            return true;
-          }
-          const style = window.getComputedStyle(nodes[i], null);
-          if (style && style.display != 'inline') {
-            return true;
-          }
-        }
-        return false;
-      });
-};
-
-/**
- * Customizable function to return the next string of words in the DOM, based
- * on provided functions to decide when to break one string and start
- * the next. This can be used to get the next sentence, line, paragraph,
- * or potentially other granularities.
- *
- * Finds the next contiguous string, starting from endCursor.  Upon exit,
- * startCursor and endCursor will surround the next string.
- *
- * The breakBefore function takes three parameters, and
- * should return true if the string should be broken before the proposed
- * next word:
- *   str The string so far.
- *   word The next word to be added.
- *   nodesCrossed The nodes crossed in reaching this next word.
- *
- * @param {Cursor} startCursor On exit, will point to the beginning of the
- *     next string.
- * @param {Cursor} endCursor The position to start searching for the next
- *     string.  On exit, will point to the end of the returned string.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @param {function(string, string, Array<string>)} breakBefore
- *     Function that takes the string so far, next word to be added, and
- *     nodes crossed, and returns true if the string should be ended before
- *     adding this word.
- * @return {?string} The next string, or null if the bottom of the
- *     document has been reached.
- */
-TraverseUtil.getNextString = function(
-    startCursor, endCursor, nodesCrossed, breakBefore) {
-  // Get the first word and set the start cursor to the start of the
-  // first word.
-  const wordStartCursor = endCursor.clone();
-  const wordEndCursor = endCursor.clone();
-  let newNodesCrossed = [];
-  let str = '';
-  let word = TraverseUtil.getNextWord(
-      wordStartCursor, wordEndCursor, newNodesCrossed);
-  if (word == null)
-    return null;
-  startCursor.copyFrom(wordStartCursor);
-
-  // Always add the first word when the string is empty, and then keep
-  // adding more words as long as breakBefore returns false
-  while (!str || !breakBefore(str, word, newNodesCrossed)) {
-    // Append this word, set the end cursor to the end of this word, and
-    // update the returned list of nodes crossed to include ones we crossed
-    // in reaching this word.
-    if (str)
-      str += ' ';
-    str += word;
-    nodesCrossed = nodesCrossed.concat(newNodesCrossed);
-    endCursor.copyFrom(wordEndCursor);
-
-    // Get the next word and go back to the top of the loop.
-    newNodesCrossed = [];
-    word = TraverseUtil.getNextWord(
-        wordStartCursor, wordEndCursor, newNodesCrossed);
-    if (word == null)
-      return str;
-  }
-
-  return str;
-};
-
-/**
- * Customizable function to return the previous string of words in the DOM,
- * based on provided functions to decide when to break one string and start
- * the next. See getNextString, above, for more details.
- *
- * Finds the previous contiguous string, starting from startCursor.  Upon exit,
- * startCursor and endCursor will surround the next string.
- *
- * @param {Cursor} startCursor The position to start searching for the
- *     previous string.  On exit, will point to the beginning of the
- *     string returned.
- * @param {Cursor} endCursor On exit, will point to the end of the
- *     string returned.
- * @param {Array<Node>} nodesCrossed Any HTML nodes crossed between the
- *     initial and final cursor position will be pushed onto this array.
- * @param {function(string, string, Array<string>)} breakBefore
- *     Function that takes the string so far, the word to be added, and
- *     nodes crossed, and returns true if the string should be ended before
- *     adding this word.
- * @return {?string} The next string, or null if the top of the
- *     document has been reached.
- */
-TraverseUtil.getPreviousString = function(
-    startCursor, endCursor, nodesCrossed, breakBefore) {
-  // Get the first word and set the end cursor to the end of the
-  // first word.
-  const wordStartCursor = startCursor.clone();
-  const wordEndCursor = startCursor.clone();
-  let newNodesCrossed = [];
-  let str = '';
-  let word = TraverseUtil.getPreviousWord(
-      wordStartCursor, wordEndCursor, newNodesCrossed);
-  if (word == null)
-    return null;
-  endCursor.copyFrom(wordEndCursor);
-
-  // Always add the first word when the string is empty, and then keep
-  // adding more words as long as breakBefore returns false
-  while (!str || !breakBefore(str, word, newNodesCrossed)) {
-    // Prepend this word, set the start cursor to the start of this word, and
-    // update the returned list of nodes crossed to include ones we crossed
-    // in reaching this word.
-    if (str)
-      str = ' ' + str;
-    str = word + str;
-    nodesCrossed = nodesCrossed.concat(newNodesCrossed);
-    startCursor.copyFrom(wordStartCursor);
-
-    // Get the previous word and go back to the top of the loop.
-    newNodesCrossed = [];
-    word = TraverseUtil.getPreviousWord(
-        wordStartCursor, wordEndCursor, newNodesCrossed);
-    if (word == null)
-      return str;
-  }
-
-  return str;
-};
diff --git a/ui/accessibility/extensions/chromevoxclassic/manifest.json.jinja2 b/ui/accessibility/extensions/chromevoxclassic/manifest.json.jinja2
index 89f534c..b463fdba 100644
--- a/ui/accessibility/extensions/chromevoxclassic/manifest.json.jinja2
+++ b/ui/accessibility/extensions/chromevoxclassic/manifest.json.jinja2
@@ -23,7 +23,7 @@
    "manifest_version": 2,
    "name": "Screen Reader",
    "options_page": "chromevox/background/options.html",
-   "permissions": [ "accessibilityPrivate", "bookmarks", "history", "notifications", "storage", "tabs", "tts", "<all_urls>" ],
+   "permissions": [ "accessibilityPrivate", "history", "notifications", "storage", "tabs", "tts", "<all_urls>" ],
    "update_url": "https://clients2.google.com/service/update2/crx",
    "version": "53.0.2784.11",
    "web_accessible_resources": [ "chromevox/background/keymaps/next_keymap.json", "chromevox/injected/api.js", "chromevox/injected/api_util.js", "chromevox/injected/mathjax.js", "chromevox/injected/mathjax_external_util.js", "webcomponents-bundle.js" ]
diff --git a/ui/android/delegated_frame_host_android.cc b/ui/android/delegated_frame_host_android.cc
index 5c16c11..8c95bc4 100644
--- a/ui/android/delegated_frame_host_android.cc
+++ b/ui/android/delegated_frame_host_android.cc
@@ -21,6 +21,7 @@
 #include "components/viz/common/quads/compositor_frame.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
 #include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/common/viz_utils.h"
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "ui/android/view_android.h"
 #include "ui/android/window_android.h"
@@ -160,25 +161,8 @@
   request->set_result_task_runner(
       base::SequencedTaskRunner::GetCurrentDefault());
 
-  if (!src_subrect.IsEmpty())
-    request->set_area(src_subrect);
-  if (!output_size.IsEmpty()) {
-    // The CopyOutputRequest API does not allow fixing the output size. Instead
-    // we have the set area and scale in such a way that it would result in the
-    // desired output size.
-    if (!request->has_area())
-      request->set_area(gfx::Rect(surface_size_in_pixels_));
-    request->set_result_selection(gfx::Rect(output_size));
-    const gfx::Rect& area = request->area();
-    // Viz would normally return an empty result for an empty area.
-    // However, this guard here is still necessary to protect against setting
-    // an illegal scaling ratio.
-    if (area.IsEmpty())
-      return;
-    request->SetScaleRatio(
-        gfx::Vector2d(area.width(), area.height()),
-        gfx::Vector2d(output_size.width(), output_size.height()));
-  }
+  viz::SetCopyOutoutRequestResultSize(request.get(), src_subrect, output_size,
+                                      surface_size_in_pixels_);
 
   host_frame_sink_manager_->RequestCopyOfOutput(surface_id, std::move(request),
                                                 capture_exact_surface_id);
diff --git a/ui/views/window/dialog_client_view.cc b/ui/views/window/dialog_client_view.cc
index 2aaa3b8..2129831b 100644
--- a/ui/views/window/dialog_client_view.cc
+++ b/ui/views/window/dialog_client_view.cc
@@ -508,10 +508,6 @@
       .SetLinkedColumnSizeLimit(layout_provider->GetDistanceMetric(
           DISTANCE_BUTTON_MAX_LINKABLE_WIDTH));
 
-  // Track which columns to link sizes under MD.
-  constexpr size_t kViewToColumnIndex[] = {1, 3, 5};
-  std::vector<size_t> columns_to_link;
-
   // Skip views that are not a button, or are a specific subclass of Button
   // that should never be linked. Otherwise, link everything.
   auto should_link = [](views::View* view) {
@@ -519,19 +515,27 @@
            !IsViewClass<ImageButton>(view);
   };
 
-  for (size_t view_index = 0; view_index < kNumButtons; ++view_index) {
-    if (views[view_index]) {
-      RemoveFillerView(view_index);
-      button_row_container_->ReorderChildView(views[view_index], view_index);
-      if (should_link(views[view_index])) {
-        columns_to_link.push_back(kViewToColumnIndex[view_index]);
-      }
+  for (size_t i = 0; i < kNumButtons; ++i) {
+    if (views[i]) {
+      RemoveFillerView(i);
+      button_row_container_->ReorderChildView(views[i], i);
     } else {
-      AddFillerView(view_index);
+      AddFillerView(i);
     }
   }
 
-  layout->LinkColumnSizes(columns_to_link);
+  {
+    std::vector<size_t> cols;
+    for (size_t i = 0; i < kNumButtons; ++i) {
+      if (should_link(views[i])) {
+        // View columns are interspersed with padding columns, so view i is at
+        // column i * 2 + 1 in the TableLayout (view 0 is in column 1, view 1 is
+        // in column 3, etc).
+        cols.push_back(i * 2 + 1);
+      }
+    }
+    layout->LinkColumnSizes(cols);
+  }
 
   // The default focus is lost when child views are added back into the dialog.
   // This restores focus if the button is still available.
diff --git a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html
index 0c373adc..5f08203b 100644
--- a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html
+++ b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html
@@ -99,7 +99,7 @@
         <cr-url-list-item url="[[item.url.url]]" title="[[item.title]]"
             description="[[item.urlForDisplay]]"
             on-click="onResultClick_" on-auxclick="onResultClick_"
-            as-anchor always-show-suffix>
+            as-anchor as-anchor-target="_blank" always-show-suffix>
           <span slot="suffix">[[item.relativeTime]]</span>
           <cr-icon-button slot="suffix" iron-icon="cr:more-vert"
               on-click="onMoreActionsClick_">
diff --git a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html.ts b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html.ts
index e8d46bcc..8cc021af 100644
--- a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html.ts
+++ b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html.ts
@@ -7,6 +7,7 @@
 import {nothing} from '//resources/lit/v3_0/lit.rollup.js';
 
 export function getHtml(this: CrDialogElement) {
+  // clang-format off
   return html`
 <dialog id="dialog" @close="${this.onNativeDialogClose_}"
     @cancel="${this.onNativeDialogCancel_}" part="dialog"
@@ -19,11 +20,12 @@
       <h2 id="title" class="title-container" tabindex="-1">
         <slot name="title"></slot>
       </h2>
-      <cr-icon-button id="close" class="icon-clear"
-          ?hidden="${!this.showCloseButton}"
-          aria-label="${this.closeText || nothing}"
-          @click="${this.cancel}" @keypress="${this.onCloseKeypress_}">
-      </cr-icon-button>
+      ${this.showCloseButton ? html`
+        <cr-icon-button id="close" class="icon-clear"
+            aria-label="${this.closeText || nothing}"
+            @click="${this.cancel}" @keypress="${this.onCloseKeypress_}">
+        </cr-icon-button>
+       ` : ''}
     </div>
     <slot name="header"></slot>
     <div class="body-container" id="container" show-bottom-shadow
@@ -34,4 +36,5 @@
     <slot name="footer"></slot>
   </div>
 </dialog>`;
+  // clang-format on
 }
diff --git a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.ts b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.ts
index 014d591..f3fd068d 100644
--- a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.ts
+++ b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.ts
@@ -25,7 +25,6 @@
 import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';
 
 import {CrContainerShadowMixinLit} from '../cr_container_shadow_mixin_lit.js';
-import type {CrIconButtonElement} from '../cr_icon_button/cr_icon_button.js';
 import type {CrInputElement} from '../cr_input/cr_input.js';
 
 import {getCss} from './cr_dialog.css.js';
@@ -35,7 +34,6 @@
 
 export interface CrDialogElement {
   $: {
-    close: CrIconButtonElement,
     dialog: HTMLDialogElement,
   };
 }
diff --git a/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html.ts b/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html.ts
index ccb2999..2219d1c9 100644
--- a/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html.ts
+++ b/ui/webui/resources/cr_elements/cr_link_row/cr_link_row.html.ts
@@ -7,10 +7,11 @@
 import type {CrLinkRowElement} from './cr_link_row.js';
 
 export function getHtml(this: CrLinkRowElement) {
+  // clang-format off
   return html`
-<cr-icon id="startIcon" .icon="${this.startIcon}" ?hidden="${!this.startIcon}"
-    aria-hidden="true">
-</cr-icon>
+    ${this.startIcon ? html`
+<cr-icon id="startIcon" .icon="${this.startIcon}" aria-hidden="true"></cr-icon>
+    `: ''}
 <div id="labelWrapper" ?hidden="${this.shouldHideLabelWrapper_()}">
   <div id="label" aria-hidden="${!this.ariaShowLabel}">
     ${this.label}
@@ -31,4 +32,5 @@
     aria-describedby="buttonAriaDescription"
     aria-labelledby="label subLabel" ?disabled="${this.disabled}">
 </cr-icon-button>`;
+  // clang-format on
 }
diff --git a/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.html.ts b/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.html.ts
index 4fd9c74..6af78ca 100644
--- a/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.html.ts
+++ b/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.html.ts
@@ -30,6 +30,7 @@
 export function getHtml(this: CrUrlListItemElement) {
   return html`
 <a id="anchor" .href="${this.url}" ?hidden="${!this.asAnchor}"
+    target="${this.asAnchorTarget}"
     aria-label="${this.getItemAriaLabel_()}"
     aria-description="${this.getItemAriaDescription_() || nothing}">
 </a>
diff --git a/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.ts b/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.ts
index 0b914ff..a259ffa 100644
--- a/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.ts
+++ b/ui/webui/resources/cr_elements/cr_url_list_item/cr_url_list_item.ts
@@ -23,7 +23,7 @@
 
 export interface CrUrlListItemElement {
   $: {
-    anchor: HTMLElement,
+    anchor: HTMLAnchorElement,
     badgesContainer: HTMLElement,
     badges: HTMLSlotElement,
     button: HTMLElement,
@@ -117,11 +117,13 @@
        * activate.
        */
       asAnchor: {type: Boolean},
+      asAnchorTarget: {type: String},
     };
   }
 
   alwaysShowSuffix: boolean = false;
   asAnchor: boolean = false;
+  asAnchorTarget: string = '_self';
   itemAriaLabel?: string;
   itemAriaDescription?: string;
   count?: number;
diff --git a/ui/webui/resources/tools/codemods/lit_migration_templates.mjs b/ui/webui/resources/tools/codemods/lit_migration_templates.mjs
index c240499..c5dd93e 100644
--- a/ui/webui/resources/tools/codemods/lit_migration_templates.mjs
+++ b/ui/webui/resources/tools/codemods/lit_migration_templates.mjs
@@ -27,8 +27,14 @@
 const LISTENER_BINDING_REGEX =
     /on-(?<eventName>[a-zA-Z-]+)="(?<listenerName>[a-zA-Z_]+)"/g;
 
+// Regular expression to extract any "${this.foo}" ocurrences in the HTML
+// template, referring to TS methods or member variables.
+const TS_REFERENCE_REGEX =
+    /"\$\{this\.(?<reference>[a-zA-Z_]+)\}"/g;
+
 function processFile(file) {
   const basename = path.basename(file, '.ts');
+  const tsFile = path.join(path.dirname(file), basename + '.ts');
   const htmlFile = path.join(path.dirname(file), basename + '.html');
   const cssFile = path.join(path.dirname(file), basename + '.css');
 
@@ -59,6 +65,19 @@
 
   // Step 5: Write updated HTML content to disk
   fs.writeFileSync(htmlFile, htmlContent, 'utf8');
+
+  // Step 6: Extract all methods/variables being referenced from the template
+  //         and if they are 'private' change them to 'protected'.
+  const references = Array.from(
+      htmlContent.matchAll(TS_REFERENCE_REGEX)).map(m => m[1]);
+  if (references.length > 0) {
+    let tsContent = fs.readFileSync(tsFile, 'utf8');
+    for (const ref of references) {
+      tsContent = tsContent.replace(`private ${ref}`, `protected ${ref}`);
+    }
+    // Step 7: Write updated TS content to disk
+    fs.writeFileSync(tsFile, tsContent, 'utf8');
+  }
 }
 
 function main() {
diff --git a/v8 b/v8
index f0366cb..06b1e2c 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit f0366cb2215c9ee342a870b52db0769aad7b20d4
+Subproject commit 06b1e2c29489af4922ca60a44347ce0ef7c36ebb